blob: 9e7573d644e31721cfecfb5d4092ac7ba5b416a3 [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':
Terry Reedy247327a2011-01-01 02:32:46 +0000455 try:
456 os.startfile(self.help_url)
457 except WindowsError as why:
458 tkMessageBox.showerror(title='Document Start Failure',
459 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000460 else:
461 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000462 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000463
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000464 def cut(self,event):
465 self.text.event_generate("<<Cut>>")
466 return "break"
467
468 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000469 if not self.text.tag_ranges("sel"):
470 # There is no selection, so do nothing and maybe interrupt.
471 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000472 self.text.event_generate("<<Copy>>")
473 return "break"
474
475 def paste(self,event):
476 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000477 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000478 return "break"
479
David Scherer7aced172000-08-15 01:13:23 +0000480 def select_all(self, event=None):
481 self.text.tag_add("sel", "1.0", "end-1c")
482 self.text.mark_set("insert", "1.0")
483 self.text.see("insert")
484 return "break"
485
486 def remove_selection(self, event=None):
487 self.text.tag_remove("sel", "1.0", "end")
488 self.text.see("insert")
489
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000490 def move_at_edge_if_selection(self, edge_index):
491 """Cursor move begins at start or end of selection
492
493 When a left/right cursor key is pressed create and return to Tkinter a
494 function which causes a cursor move from the associated edge of the
495 selection.
496
497 """
498 self_text_index = self.text.index
499 self_text_mark_set = self.text.mark_set
500 edges_table = ("sel.first+1c", "sel.last-1c")
501 def move_at_edge(event):
502 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
503 try:
504 self_text_index("sel.first")
505 self_text_mark_set("insert", edges_table[edge_index])
506 except TclError:
507 pass
508 return move_at_edge
509
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000510 def del_word_left(self, event):
511 self.text.event_generate('<Meta-Delete>')
512 return "break"
513
514 def del_word_right(self, event):
515 self.text.event_generate('<Meta-d>')
516 return "break"
517
Steven M. Gavac5976402002-01-04 03:06:08 +0000518 def find_event(self, event):
519 SearchDialog.find(self.text)
520 return "break"
521
522 def find_again_event(self, event):
523 SearchDialog.find_again(self.text)
524 return "break"
525
526 def find_selection_event(self, event):
527 SearchDialog.find_selection(self.text)
528 return "break"
529
530 def find_in_files_event(self, event):
531 GrepDialog.grep(self.text, self.io, self.flist)
532 return "break"
533
534 def replace_event(self, event):
535 ReplaceDialog.replace(self.text)
536 return "break"
537
538 def goto_line_event(self, event):
539 text = self.text
540 lineno = tkSimpleDialog.askinteger("Goto",
541 "Go to line number:",parent=text)
542 if lineno is None:
543 return "break"
544 if lineno <= 0:
545 text.bell()
546 return "break"
547 text.mark_set("insert", "%d.0" % lineno)
548 text.see("insert")
549
David Scherer7aced172000-08-15 01:13:23 +0000550 def open_module(self, event=None):
551 # XXX Shouldn't this be in IOBinding or in FileList?
552 try:
553 name = self.text.get("sel.first", "sel.last")
554 except TclError:
555 name = ""
556 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000557 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000558 name = tkSimpleDialog.askstring("Module",
559 "Enter the name of a Python module\n"
560 "to search on sys.path and open:",
561 parent=self.text, initialvalue=name)
562 if name:
563 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000564 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000565 return
David Scherer7aced172000-08-15 01:13:23 +0000566 # XXX Ought to insert current file's directory in front of path
567 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000568 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000569 except (NameError, ImportError), msg:
570 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
571 return
572 if type != imp.PY_SOURCE:
573 tkMessageBox.showerror("Unsupported type",
574 "%s is not a source module" % name, parent=self.text)
575 return
576 if f:
577 f.close()
578 if self.flist:
579 self.flist.open(file)
580 else:
581 self.io.loadfile(file)
582
583 def open_class_browser(self, event=None):
584 filename = self.io.filename
585 if not filename:
586 tkMessageBox.showerror(
587 "No filename",
588 "This buffer has no associated filename",
589 master=self.text)
590 self.text.focus_set()
591 return None
592 head, tail = os.path.split(filename)
593 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000594 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000595 ClassBrowser.ClassBrowser(self.flist, base, [head])
596
597 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000598 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000599 PathBrowser.PathBrowser(self.flist)
600
601 def gotoline(self, lineno):
602 if lineno is not None and lineno > 0:
603 self.text.mark_set("insert", "%d.0" % lineno)
604 self.text.tag_remove("sel", "1.0", "end")
605 self.text.tag_add("sel", "insert", "insert +1l")
606 self.center()
607
608 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000609 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000610 return True
David Scherer7aced172000-08-15 01:13:23 +0000611 base, ext = os.path.splitext(os.path.basename(filename))
612 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000613 return True
David Scherer7aced172000-08-15 01:13:23 +0000614 try:
615 f = open(filename)
616 line = f.readline()
617 f.close()
618 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000619 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000620 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000621
622 def close_hook(self):
623 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000624 self.flist.unregister_maybe_terminate(self)
625 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000626
627 def set_close_hook(self, close_hook):
628 self.close_hook = close_hook
629
630 def filename_change_hook(self):
631 if self.flist:
632 self.flist.filename_changed_edit(self)
633 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000634 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000635 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000636
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000637 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000638 if self.color:
639 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000640 if self.ispythonsource(self.io.filename):
641 self.color = self.ColorDelegator()
642 # can add more colorizers here...
643 if self.color:
644 self.per.removefilter(self.undo)
645 self.per.insertfilter(self.color)
646 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000647
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000648 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000649 if not self.color:
650 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000651 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000652 self.per.removefilter(self.color)
653 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000654
Steven M. Gavab77d3432002-03-02 07:16:21 +0000655 def ResetColorizer(self):
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000656 "Update the colour theme"
657 # Called from self.filename_change_hook and from configDialog.py
658 self._rmcolorizer()
659 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000660 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000661 normal_colors = idleConf.GetHighlight(theme, 'normal')
662 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
663 select_colors = idleConf.GetHighlight(theme, 'hilite')
664 self.text.config(
665 foreground=normal_colors['foreground'],
666 background=normal_colors['background'],
667 insertbackground=cursor_color,
668 selectforeground=select_colors['foreground'],
669 selectbackground=select_colors['background'],
670 )
David Scherer7aced172000-08-15 01:13:23 +0000671
Steven M. Gavab1585412002-03-12 00:21:56 +0000672 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000673 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000674 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000675 fontWeight='normal'
676 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
677 fontWeight='bold'
678 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
679 idleConf.GetOption('main','EditorWindow','font-size'),
680 fontWeight))
681
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000682 def RemoveKeybindings(self):
683 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000684 # 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 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000687 self.text.event_delete(event, *keylist)
688 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000689 xkeydefs = idleConf.GetExtensionBindings(extensionName)
690 if xkeydefs:
691 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000692 self.text.event_delete(event, *keylist)
693
694 def ApplyKeybindings(self):
695 "Update the keybindings after they are changed"
696 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000697 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000698 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000699 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000700 xkeydefs = idleConf.GetExtensionBindings(extensionName)
701 if xkeydefs:
702 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000703 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000704 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000705 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000706 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000707 for item in menu[1]:
708 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000709 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000710 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000711 menu = self.menudict[menubarItem]
712 end = menu.index(END) + 1
713 for index in range(0, end):
714 if menu.type(index) == 'command':
715 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000716 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000717 itemName = menu.entrycget(index, 'label')
718 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000719 if menubarItem in menuEventDict:
720 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000721 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000722 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000723 accel = get_accelerator(keydefs, event)
724 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000725
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000726 def set_notabs_indentwidth(self):
727 "Update the indentwidth if changed and not using tabs in this window"
728 # Called from configDialog.py
729 if not self.usetabs:
730 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
731 type='int')
732
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000733 def reset_help_menu_entries(self):
734 "Update the additional help entries on the Help menu"
735 help_list = idleConf.GetAllExtraHelpSourcesList()
736 helpmenu = self.menudict['help']
737 # first delete the extra help entries, if any
738 helpmenu_length = helpmenu.index(END)
739 if helpmenu_length > self.base_helpmenu_length:
740 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
741 # then rebuild them
742 if help_list:
743 helpmenu.add_separator()
744 for entry in help_list:
745 cmd = self.__extra_help_callback(entry[1])
746 helpmenu.add_command(label=entry[0], command=cmd)
747 # and update the menu dictionary
748 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000749
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000750 def __extra_help_callback(self, helpfile):
751 "Create a callback with the helpfile value frozen at definition time"
752 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000753 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000754 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000755 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000756 try:
757 os.startfile(helpfile)
758 except WindowsError as why:
759 tkMessageBox.showerror(title='Document Start Failure',
760 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000761 else:
762 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000763 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000764
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000765 def update_recent_files_list(self, new_file=None):
766 "Load and update the recent files list and menus"
767 rf_list = []
768 if os.path.exists(self.recent_files_path):
769 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000770 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000771 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000772 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000773 rf_list_file.close()
774 if new_file:
775 new_file = os.path.abspath(new_file) + '\n'
776 if new_file in rf_list:
777 rf_list.remove(new_file) # move to top
778 rf_list.insert(0, new_file)
779 # clean and save the recent files list
780 bad_paths = []
781 for path in rf_list:
782 if '\0' in path or not os.path.exists(path[0:-1]):
783 bad_paths.append(path)
784 rf_list = [path for path in rf_list if path not in bad_paths]
785 ulchars = "1234567890ABCDEFGHIJK"
786 rf_list = rf_list[0:len(ulchars)]
787 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000788 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000789 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000790 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000791 rf_file.close()
792 # for each edit window instance, construct the recent files menu
793 for instance in self.top.instance_dict.keys():
794 menu = instance.recent_files_menu
795 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000796 for i, file_name in enumerate(rf_list):
797 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000798 # make unicode string to display non-ASCII chars correctly
799 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000800 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000801 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000802 command=callback,
803 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000804
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000805 def __recent_file_callback(self, file_name):
806 def open_recent_file(fn_closure=file_name):
807 self.io.open(editFile=fn_closure)
808 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000809
David Scherer7aced172000-08-15 01:13:23 +0000810 def saved_change_hook(self):
811 short = self.short_title()
812 long = self.long_title()
813 if short and long:
814 title = short + " - " + long
815 elif short:
816 title = short
817 elif long:
818 title = long
819 else:
820 title = "Untitled"
821 icon = short or long or title
822 if not self.get_saved():
823 title = "*%s*" % title
824 icon = "*%s" % icon
825 self.top.wm_title(title)
826 self.top.wm_iconname(icon)
827
828 def get_saved(self):
829 return self.undo.get_saved()
830
831 def set_saved(self, flag):
832 self.undo.set_saved(flag)
833
834 def reset_undo(self):
835 self.undo.reset_undo()
836
837 def short_title(self):
838 filename = self.io.filename
839 if filename:
840 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000841 # return unicode string to display non-ASCII chars correctly
842 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000843
844 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000845 # return unicode string to display non-ASCII chars correctly
846 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000847
848 def center_insert_event(self, event):
849 self.center()
850
851 def center(self, mark="insert"):
852 text = self.text
853 top, bot = self.getwindowlines()
854 lineno = self.getlineno(mark)
855 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000856 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000857 text.yview(float(newtop))
858
859 def getwindowlines(self):
860 text = self.text
861 top = self.getlineno("@0,0")
862 bot = self.getlineno("@0,65535")
863 if top == bot and text.winfo_height() == 1:
864 # Geometry manager hasn't run yet
865 height = int(text['height'])
866 bot = top + height - 1
867 return top, bot
868
869 def getlineno(self, mark="insert"):
870 text = self.text
871 return int(float(text.index(mark)))
872
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000873 def get_geometry(self):
874 "Return (width, height, x, y)"
875 geom = self.top.wm_geometry()
876 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
877 tuple = (map(int, m.groups()))
878 return tuple
879
David Scherer7aced172000-08-15 01:13:23 +0000880 def close_event(self, event):
881 self.close()
882
883 def maybesave(self):
884 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000885 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000886 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000887 self.top.deiconify()
888 self.top.lower()
889 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000890 return self.io.maybesave()
891
892 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000893 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +0000894 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000895 self._close()
896 return reply
897
898 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000899 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000900 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000901 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000902 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000903 self.io.close()
904 self.io = None
905 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000906 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000907 self.color.close(False)
908 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000909 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000910 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000911 self.per.close()
912 self.per = None
913 self.top.destroy()
914 if self.close_hook:
915 # unless override: unregister from flist, terminate if last window
916 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000917
918 def load_extensions(self):
919 self.extensions = {}
920 self.load_standard_extensions()
921
922 def unload_extensions(self):
923 for ins in self.extensions.values():
924 if hasattr(ins, "close"):
925 ins.close()
926 self.extensions = {}
927
928 def load_standard_extensions(self):
929 for name in self.get_standard_extension_names():
930 try:
931 self.load_extension(name)
932 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000933 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +0000934 import traceback
935 traceback.print_exc()
936
937 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000938 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000939
940 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000941 try:
942 mod = __import__(name, globals(), locals(), [])
943 except ImportError:
944 print "\nFailed to import extension: ", name
945 return
David Scherer7aced172000-08-15 01:13:23 +0000946 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000947 keydefs = idleConf.GetExtensionBindings(name)
948 if hasattr(cls, "menudefs"):
949 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000950 ins = cls(self)
951 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000952 if keydefs:
953 self.apply_bindings(keydefs)
954 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000955 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000956 while methodname[:1] == '<':
957 methodname = methodname[1:]
958 while methodname[-1:] == '>':
959 methodname = methodname[:-1]
960 methodname = methodname + "_event"
961 if hasattr(ins, methodname):
962 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000963
964 def apply_bindings(self, keydefs=None):
965 if keydefs is None:
966 keydefs = self.Bindings.default_keydefs
967 text = self.text
968 text.keydefs = keydefs
969 for event, keylist in keydefs.items():
970 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000971 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000972
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000973 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000974 """Add appropriate entries to the menus and submenus
975
976 Menus that are absent or None in self.menudict are ignored.
977 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000978 if menudefs is None:
979 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000980 if keydefs is None:
981 keydefs = self.Bindings.default_keydefs
982 menudict = self.menudict
983 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000984 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000985 menu = menudict.get(mname)
986 if not menu:
987 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000988 for entry in entrylist:
989 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000990 menu.add_separator()
991 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000992 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000993 checkbutton = (label[:1] == '!')
994 if checkbutton:
995 label = label[1:]
996 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000997 accelerator = get_accelerator(keydefs, eventname)
998 def command(text=text, eventname=eventname):
999 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001000 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001001 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001002 menu.add_checkbutton(label=label, underline=underline,
1003 command=command, accelerator=accelerator,
1004 variable=var)
1005 else:
1006 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001007 command=command,
1008 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001009
1010 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001011 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001012 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001013 value = var.get()
1014 return value
1015 else:
1016 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001017
1018 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001019 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001020 if var:
1021 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001022 else:
1023 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001024
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001025 def get_var_obj(self, name, vartype=None):
1026 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001027 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001028 # create a Tkinter variable object with self.text as master:
1029 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001030 return var
1031
1032 # Tk implementations of "virtual text methods" -- each platform
1033 # reusing IDLE's support code needs to define these for its GUI's
1034 # flavor of widget.
1035
1036 # Is character at text_index in a Python string? Return 0 for
1037 # "guaranteed no", true for anything else. This info is expensive
1038 # to compute ab initio, but is probably already known by the
1039 # platform's colorizer.
1040
1041 def is_char_in_string(self, text_index):
1042 if self.color:
1043 # Return true iff colorizer hasn't (re)gotten this far
1044 # yet, or the character is tagged as being in a string
1045 return self.text.tag_prevrange("TODO", text_index) or \
1046 "STRING" in self.text.tag_names(text_index)
1047 else:
1048 # The colorizer is missing: assume the worst
1049 return 1
1050
1051 # If a selection is defined in the text widget, return (start,
1052 # end) as Tkinter text indices, otherwise return (None, None)
1053 def get_selection_indices(self):
1054 try:
1055 first = self.text.index("sel.first")
1056 last = self.text.index("sel.last")
1057 return first, last
1058 except TclError:
1059 return None, None
1060
1061 # Return the text widget's current view of what a tab stop means
1062 # (equivalent width in spaces).
1063
1064 def get_tabwidth(self):
1065 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1066 return int(current)
1067
1068 # Set the text widget's current view of what a tab stop means.
1069
1070 def set_tabwidth(self, newtabwidth):
1071 text = self.text
1072 if self.get_tabwidth() != newtabwidth:
1073 pixels = text.tk.call("font", "measure", text["font"],
1074 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001075 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001076 text.configure(tabs=pixels)
1077
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001078 # If ispythonsource and guess are true, guess a good value for
1079 # indentwidth based on file content (if possible), and if
1080 # indentwidth != tabwidth set usetabs false.
1081 # In any case, adjust the Text widget's view of what a tab
1082 # character means.
1083
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001084 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001085 if guess and ispythonsource:
1086 i = self.guess_indent()
1087 if 2 <= i <= 8:
1088 self.indentwidth = i
1089 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001090 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001091 self.set_tabwidth(self.tabwidth)
1092
1093 def smart_backspace_event(self, event):
1094 text = self.text
1095 first, last = self.get_selection_indices()
1096 if first and last:
1097 text.delete(first, last)
1098 text.mark_set("insert", first)
1099 return "break"
1100 # Delete whitespace left, until hitting a real char or closest
1101 # preceding virtual tab stop.
1102 chars = text.get("insert linestart", "insert")
1103 if chars == '':
1104 if text.compare("insert", ">", "1.0"):
1105 # easy: delete preceding newline
1106 text.delete("insert-1c")
1107 else:
1108 text.bell() # at start of buffer
1109 return "break"
1110 if chars[-1] not in " \t":
1111 # easy: delete preceding real char
1112 text.delete("insert-1c")
1113 return "break"
1114 # Ick. It may require *inserting* spaces if we back up over a
1115 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001116 tabwidth = self.tabwidth
1117 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001118 assert have > 0
1119 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001120 # Debug prompt is multilined....
1121 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001122 ncharsdeleted = 0
1123 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001124 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001125 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001126 chars = chars[:-1]
1127 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001128 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001129 if have <= want or chars[-1] not in " \t":
1130 break
1131 text.undo_block_start()
1132 text.delete("insert-%dc" % ncharsdeleted, "insert")
1133 if have < want:
1134 text.insert("insert", ' ' * (want - have))
1135 text.undo_block_stop()
1136 return "break"
1137
1138 def smart_indent_event(self, event):
1139 # if intraline selection:
1140 # delete it
1141 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001142 # do indent-region
1143 # else:
1144 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001145 text = self.text
1146 first, last = self.get_selection_indices()
1147 text.undo_block_start()
1148 try:
1149 if first and last:
1150 if index2line(first) != index2line(last):
1151 return self.indent_region_event(event)
1152 text.delete(first, last)
1153 text.mark_set("insert", first)
1154 prefix = text.get("insert linestart", "insert")
1155 raw, effective = classifyws(prefix, self.tabwidth)
1156 if raw == len(prefix):
1157 # only whitespace to the left
1158 self.reindent_to(effective + self.indentwidth)
1159 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001160 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001161 if self.usetabs:
1162 pad = '\t'
1163 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001164 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001165 n = self.indentwidth
1166 pad = ' ' * (n - effective % n)
1167 text.insert("insert", pad)
1168 text.see("insert")
1169 return "break"
1170 finally:
1171 text.undo_block_stop()
1172
1173 def newline_and_indent_event(self, event):
1174 text = self.text
1175 first, last = self.get_selection_indices()
1176 text.undo_block_start()
1177 try:
1178 if first and last:
1179 text.delete(first, last)
1180 text.mark_set("insert", first)
1181 line = text.get("insert linestart", "insert")
1182 i, n = 0, len(line)
1183 while i < n and line[i] in " \t":
1184 i = i+1
1185 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001186 # the cursor is in or at leading indentation in a continuation
1187 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001188 text.insert("insert linestart", '\n')
1189 return "break"
1190 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001191 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001192 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001193 last_line_of_prompt = sys.ps1.split('\n')[-1]
1194 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001195 line = line[:-1]
1196 i = i+1
1197 if i:
1198 text.delete("insert - %d chars" % i, "insert")
1199 # strip whitespace after insert point
1200 while text.get("insert") in " \t":
1201 text.delete("insert")
1202 # start new line
1203 text.insert("insert", '\n')
1204
1205 # adjust indentation for continuations and block
1206 # open/close first need to find the last stmt
1207 lno = index2line(text.index('insert'))
1208 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001209 if not self.context_use_ps1:
1210 for context in self.num_context_lines:
1211 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001212 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001213 rawtext = text.get(startatindex, "insert")
1214 y.set_str(rawtext)
1215 bod = y.find_good_parse_start(
1216 self.context_use_ps1,
1217 self._build_char_in_string_func(startatindex))
1218 if bod is not None or startat == 1:
1219 break
1220 y.set_lo(bod or 0)
1221 else:
1222 r = text.tag_prevrange("console", "insert")
1223 if r:
1224 startatindex = r[1]
1225 else:
1226 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001227 rawtext = text.get(startatindex, "insert")
1228 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001229 y.set_lo(0)
1230
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001231 c = y.get_continuation_type()
1232 if c != PyParse.C_NONE:
1233 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001234 if c == PyParse.C_STRING_FIRST_LINE:
1235 # after the first line of a string; do not indent at all
1236 pass
1237 elif c == PyParse.C_STRING_NEXT_LINES:
1238 # inside a string which started before this line;
1239 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001240 text.insert("insert", indent)
1241 elif c == PyParse.C_BRACKET:
1242 # line up with the first (if any) element of the
1243 # last open bracket structure; else indent one
1244 # level beyond the indent of the line with the
1245 # last open bracket
1246 self.reindent_to(y.compute_bracket_indent())
1247 elif c == PyParse.C_BACKSLASH:
1248 # if more than one line in this stmt already, just
1249 # mimic the current indent; else if initial line
1250 # has a start on an assignment stmt, indent to
1251 # beyond leftmost =; else to beyond first chunk of
1252 # non-whitespace on initial line
1253 if y.get_num_lines_in_stmt() > 1:
1254 text.insert("insert", indent)
1255 else:
1256 self.reindent_to(y.compute_backslash_indent())
1257 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001258 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001259 return "break"
1260
1261 # This line starts a brand new stmt; indent relative to
1262 # indentation of initial line of closest preceding
1263 # interesting stmt.
1264 indent = y.get_base_indent_string()
1265 text.insert("insert", indent)
1266 if y.is_block_opener():
1267 self.smart_indent_event(event)
1268 elif indent and y.is_block_closer():
1269 self.smart_backspace_event(event)
1270 return "break"
1271 finally:
1272 text.see("insert")
1273 text.undo_block_stop()
1274
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001275 # Our editwin provides a is_char_in_string function that works
1276 # with a Tk text index, but PyParse only knows about offsets into
1277 # a string. This builds a function for PyParse that accepts an
1278 # offset.
1279
1280 def _build_char_in_string_func(self, startindex):
1281 def inner(offset, _startindex=startindex,
1282 _icis=self.is_char_in_string):
1283 return _icis(_startindex + "+%dc" % offset)
1284 return inner
1285
1286 def indent_region_event(self, event):
1287 head, tail, chars, lines = self.get_region()
1288 for pos in range(len(lines)):
1289 line = lines[pos]
1290 if line:
1291 raw, effective = classifyws(line, self.tabwidth)
1292 effective = effective + self.indentwidth
1293 lines[pos] = self._make_blanks(effective) + line[raw:]
1294 self.set_region(head, tail, chars, lines)
1295 return "break"
1296
1297 def dedent_region_event(self, event):
1298 head, tail, chars, lines = self.get_region()
1299 for pos in range(len(lines)):
1300 line = lines[pos]
1301 if line:
1302 raw, effective = classifyws(line, self.tabwidth)
1303 effective = max(effective - self.indentwidth, 0)
1304 lines[pos] = self._make_blanks(effective) + line[raw:]
1305 self.set_region(head, tail, chars, lines)
1306 return "break"
1307
1308 def comment_region_event(self, event):
1309 head, tail, chars, lines = self.get_region()
1310 for pos in range(len(lines) - 1):
1311 line = lines[pos]
1312 lines[pos] = '##' + line
1313 self.set_region(head, tail, chars, lines)
1314
1315 def uncomment_region_event(self, event):
1316 head, tail, chars, lines = self.get_region()
1317 for pos in range(len(lines)):
1318 line = lines[pos]
1319 if not line:
1320 continue
1321 if line[:2] == '##':
1322 line = line[2:]
1323 elif line[:1] == '#':
1324 line = line[1:]
1325 lines[pos] = line
1326 self.set_region(head, tail, chars, lines)
1327
1328 def tabify_region_event(self, event):
1329 head, tail, chars, lines = self.get_region()
1330 tabwidth = self._asktabwidth()
1331 for pos in range(len(lines)):
1332 line = lines[pos]
1333 if line:
1334 raw, effective = classifyws(line, tabwidth)
1335 ntabs, nspaces = divmod(effective, tabwidth)
1336 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1337 self.set_region(head, tail, chars, lines)
1338
1339 def untabify_region_event(self, event):
1340 head, tail, chars, lines = self.get_region()
1341 tabwidth = self._asktabwidth()
1342 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001343 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001344 self.set_region(head, tail, chars, lines)
1345
1346 def toggle_tabs_event(self, event):
1347 if self.askyesno(
1348 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001349 "Turn tabs " + ("on", "off")[self.usetabs] +
1350 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001351 ("will be", "remains at")[self.usetabs] + " 8." +
1352 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001353 parent=self.text):
1354 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001355 # Try to prevent inconsistent indentation.
1356 # User must change indent width manually after using tabs.
1357 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001358 return "break"
1359
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001360 # XXX this isn't bound to anything -- see tabwidth comments
1361## def change_tabwidth_event(self, event):
1362## new = self._asktabwidth()
1363## if new != self.tabwidth:
1364## self.tabwidth = new
1365## self.set_indentation_params(0, guess=0)
1366## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001367
1368 def change_indentwidth_event(self, event):
1369 new = self.askinteger(
1370 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001371 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001372 parent=self.text,
1373 initialvalue=self.indentwidth,
1374 minvalue=2,
1375 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001376 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001377 self.indentwidth = new
1378 return "break"
1379
1380 def get_region(self):
1381 text = self.text
1382 first, last = self.get_selection_indices()
1383 if first and last:
1384 head = text.index(first + " linestart")
1385 tail = text.index(last + "-1c lineend +1c")
1386 else:
1387 head = text.index("insert linestart")
1388 tail = text.index("insert lineend +1c")
1389 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001390 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001391 return head, tail, chars, lines
1392
1393 def set_region(self, head, tail, chars, lines):
1394 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001395 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001396 if newchars == chars:
1397 text.bell()
1398 return
1399 text.tag_remove("sel", "1.0", "end")
1400 text.mark_set("insert", head)
1401 text.undo_block_start()
1402 text.delete(head, tail)
1403 text.insert(head, newchars)
1404 text.undo_block_stop()
1405 text.tag_add("sel", head, "insert")
1406
1407 # Make string that displays as n leading blanks.
1408
1409 def _make_blanks(self, n):
1410 if self.usetabs:
1411 ntabs, nspaces = divmod(n, self.tabwidth)
1412 return '\t' * ntabs + ' ' * nspaces
1413 else:
1414 return ' ' * n
1415
1416 # Delete from beginning of line to insert point, then reinsert
1417 # column logical (meaning use tabs if appropriate) spaces.
1418
1419 def reindent_to(self, column):
1420 text = self.text
1421 text.undo_block_start()
1422 if text.compare("insert linestart", "!=", "insert"):
1423 text.delete("insert linestart", "insert")
1424 if column:
1425 text.insert("insert", self._make_blanks(column))
1426 text.undo_block_stop()
1427
1428 def _asktabwidth(self):
1429 return self.askinteger(
1430 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001431 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001432 parent=self.text,
1433 initialvalue=self.indentwidth,
1434 minvalue=2,
1435 maxvalue=16) or self.tabwidth
1436
1437 # Guess indentwidth from text content.
1438 # Return guessed indentwidth. This should not be believed unless
1439 # it's in a reasonable range (e.g., it will be 0 if no indented
1440 # blocks are found).
1441
1442 def guess_indent(self):
1443 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1444 if opener and indented:
1445 raw, indentsmall = classifyws(opener, self.tabwidth)
1446 raw, indentlarge = classifyws(indented, self.tabwidth)
1447 else:
1448 indentsmall = indentlarge = 0
1449 return indentlarge - indentsmall
1450
1451# "line.col" -> line, as an int
1452def index2line(index):
1453 return int(float(index))
1454
1455# Look at the leading whitespace in s.
1456# Return pair (# of leading ws characters,
1457# effective # of leading blanks after expanding
1458# tabs to width tabwidth)
1459
1460def classifyws(s, tabwidth):
1461 raw = effective = 0
1462 for ch in s:
1463 if ch == ' ':
1464 raw = raw + 1
1465 effective = effective + 1
1466 elif ch == '\t':
1467 raw = raw + 1
1468 effective = (effective // tabwidth + 1) * tabwidth
1469 else:
1470 break
1471 return raw, effective
1472
1473import tokenize
1474_tokenize = tokenize
1475del tokenize
1476
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001477class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001478
1479 # .run() chews over the Text widget, looking for a block opener
1480 # and the stmt following it. Returns a pair,
1481 # (line containing block opener, line containing stmt)
1482 # Either or both may be None.
1483
1484 def __init__(self, text, tabwidth):
1485 self.text = text
1486 self.tabwidth = tabwidth
1487 self.i = self.finished = 0
1488 self.blkopenline = self.indentedline = None
1489
1490 def readline(self):
1491 if self.finished:
1492 return ""
1493 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001494 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001495 if self.text.compare(mark, ">=", "end"):
1496 return ""
1497 return self.text.get(mark, mark + " lineend+1c")
1498
1499 def tokeneater(self, type, token, start, end, line,
1500 INDENT=_tokenize.INDENT,
1501 NAME=_tokenize.NAME,
1502 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1503 if self.finished:
1504 pass
1505 elif type == NAME and token in OPENERS:
1506 self.blkopenline = line
1507 elif type == INDENT and self.blkopenline:
1508 self.indentedline = line
1509 self.finished = 1
1510
1511 def run(self):
1512 save_tabsize = _tokenize.tabsize
1513 _tokenize.tabsize = self.tabwidth
1514 try:
1515 try:
1516 _tokenize.tokenize(self.readline, self.tokeneater)
1517 except _tokenize.TokenError:
1518 # since we cut off the tokenizer early, we can trigger
1519 # spurious errors
1520 pass
1521 finally:
1522 _tokenize.tabsize = save_tabsize
1523 return self.blkopenline, self.indentedline
1524
1525### end autoindent code ###
1526
David Scherer7aced172000-08-15 01:13:23 +00001527def prepstr(s):
1528 # Helper to extract the underscore from a string, e.g.
1529 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001530 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001531 if i >= 0:
1532 s = s[:i] + s[i+1:]
1533 return i, s
1534
1535
1536keynames = {
1537 'bracketleft': '[',
1538 'bracketright': ']',
1539 'slash': '/',
1540}
1541
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001542def get_accelerator(keydefs, eventname):
1543 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001544 if not keylist:
1545 return ""
1546 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001547 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001548 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1549 s = re.sub("Key-", "", s)
1550 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1551 s = re.sub("Control-", "Ctrl-", s)
1552 s = re.sub("-", "+", s)
1553 s = re.sub("><", " ", s)
1554 s = re.sub("<", "", s)
1555 s = re.sub(">", "", s)
1556 return s
1557
1558
1559def fixwordbreaks(root):
1560 # Make sure that Tk's double-click and next/previous word
1561 # operations use our definition of a word (i.e. an identifier)
1562 tk = root.tk
1563 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1564 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1565 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1566
1567
1568def test():
1569 root = Tk()
1570 fixwordbreaks(root)
1571 root.withdraw()
1572 if sys.argv[1:]:
1573 filename = sys.argv[1]
1574 else:
1575 filename = None
1576 edit = EditorWindow(root=root, filename=filename)
1577 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001578 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001579 root.mainloop()
1580 root.destroy()
1581
1582if __name__ == '__main__':
1583 test()