blob: cf3fad3c1ec183c276ba5ad4c9e5936e11f2264f [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__
Raymond Hettinger179816d2011-04-12 18:54:46 -070051 if descr[2] != imp.PY_SOURCE:
52 # If all of the above fails and didn't raise an exception,fallback
53 # to a straight import which can find __init__.py in a package.
54 m = __import__(fullname)
55 try:
56 filename = m.__file__
57 except AttributeError:
58 pass
59 else:
60 file = None
61 base, ext = os.path.splitext(filename)
62 if ext == '.pyc':
63 ext = '.py'
64 filename = base + ext
65 descr = filename, None, imp.PY_SOURCE
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000066 return file, filename, descr
67
Terry Jan Reedyadb87e22012-02-05 15:10:55 -050068
69class HelpDialog(object):
70
71 def __init__(self):
72 self.parent = None # parent of help window
73 self.dlg = None # the help window iteself
74
75 def display(self, parent, near=None):
76 """ Display the help dialog.
77
78 parent - parent widget for the help window
79
80 near - a Toplevel widget (e.g. EditorWindow or PyShell)
81 to use as a reference for placing the help window
82 """
83 if self.dlg is None:
84 self.show_dialog(parent)
85 if near:
86 self.nearwindow(near)
87
88 def show_dialog(self, parent):
89 self.parent = parent
90 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
91 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
92 dlg.bind('<Destroy>', self.destroy, '+')
93
94 def nearwindow(self, near):
95 # Place the help dialog near the window specified by parent.
96 # Note - this may not reposition the window in Metacity
97 # if "/apps/metacity/general/disable_workarounds" is enabled
98 dlg = self.dlg
99 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
100 dlg.withdraw()
101 dlg.geometry("=+%d+%d" % geom)
102 dlg.deiconify()
103 dlg.lift()
104
105 def destroy(self, ev=None):
106 self.dlg = None
107 self.parent = None
108
109helpDialog = HelpDialog() # singleton instance
110
111
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000112class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +0000113 from idlelib.Percolator import Percolator
114 from idlelib.ColorDelegator import ColorDelegator
115 from idlelib.UndoDelegator import UndoDelegator
116 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
117 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +0000118 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +0000119 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000120
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000121 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000122
123 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000124 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +0000125 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000126 if sys.platform.count('linux'):
127 # look for html docs in a couple of standard places
128 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
129 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
130 dochome = '/var/www/html/python/index.html'
131 else:
132 basepath = '/usr/share/doc/' # standard location
133 dochome = os.path.join(basepath, pyver,
134 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000135 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +0000136 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +0000137 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000138 if os.path.isfile(chmfile):
139 dochome = chmfile
Ronald Oussoren19302d92006-06-11 14:33:36 +0000140 elif macosxSupport.runningAsOSXApp():
141 # documentation is stored inside the python framework
142 dochome = os.path.join(sys.prefix,
143 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000144 dochome = os.path.normpath(dochome)
145 if os.path.isfile(dochome):
146 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +0000147 if sys.platform == 'darwin':
148 # Safari requires real file:-URLs
149 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000150 else:
Raymond Hettinger7b4c2be2009-01-20 10:46:23 +0000151 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000152 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000153 self.flist = flist
154 root = root or flist.root
155 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +0000156 try:
157 sys.ps1
158 except AttributeError:
159 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000160 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000161 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000162 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000163 self.tkinter_vars = flist.vars
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200164 #self.top.instance_dict makes flist.inversedict available to
165 #configDialog.py so it can access all EditorWindow instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000166 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000167 else:
168 self.tkinter_vars = {} # keys: Tkinter event names
169 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000170 self.top.instance_dict = {}
171 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000172 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000173 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000174 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200175 self.width = idleConf.GetOption('main','EditorWindow','width', type='int')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000176 text_options = {
177 'name': 'text',
178 'padx': 5,
179 'wrap': 'none',
180 'width': self.width,
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200181 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')}
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000182 if TkVersion >= 8.5:
183 # Starting with tk 8.5 we have to set the new tabstyle option
184 # to 'wordprocessor' to achieve the same display of tabs as in
185 # older tk versions.
186 text_options['tabstyle'] = 'wordprocessor'
187 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000188 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000189
190 self.createmenubar()
191 self.apply_bindings()
192
193 self.top.protocol("WM_DELETE_WINDOW", self.close)
194 self.top.bind("<<close-window>>", self.close_event)
Ronald Oussoren17db4952006-07-23 09:41:09 +0000195 if macosxSupport.runningAsOSXApp():
196 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000197 text.bind('<<close-window>>', self.close_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000198 # Some OS X systems have only one mouse button,
199 # so use control-click for pulldown menus there.
200 # (Note, AquaTk defines <2> as the right button if
201 # present and the Tk Text widget already binds <2>.)
202 text.bind("<Control-Button-1>",self.right_menu_event)
203 else:
204 # Elsewhere, use right-click for pulldown menus.
205 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000206 text.bind("<<cut>>", self.cut)
207 text.bind("<<copy>>", self.copy)
208 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000209 text.bind("<<center-insert>>", self.center_insert_event)
210 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000211 text.bind("<<python-docs>>", self.python_docs)
212 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000213 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000214 text.bind("<<open-module>>", self.open_module)
215 text.bind("<<do-nothing>>", lambda event: "break")
216 text.bind("<<select-all>>", self.select_all)
217 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000218 text.bind("<<find>>", self.find_event)
219 text.bind("<<find-again>>", self.find_again_event)
220 text.bind("<<find-in-files>>", self.find_in_files_event)
221 text.bind("<<find-selection>>", self.find_selection_event)
222 text.bind("<<replace>>", self.replace_event)
223 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000224 text.bind("<<smart-backspace>>",self.smart_backspace_event)
225 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
226 text.bind("<<smart-indent>>",self.smart_indent_event)
227 text.bind("<<indent-region>>",self.indent_region_event)
228 text.bind("<<dedent-region>>",self.dedent_region_event)
229 text.bind("<<comment-region>>",self.comment_region_event)
230 text.bind("<<uncomment-region>>",self.uncomment_region_event)
231 text.bind("<<tabify-region>>",self.tabify_region_event)
232 text.bind("<<untabify-region>>",self.untabify_region_event)
233 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
234 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000235 text.bind("<Left>", self.move_at_edge_if_selection(0))
236 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000237 text.bind("<<del-word-left>>", self.del_word_left)
238 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000239 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000240
David Scherer7aced172000-08-15 01:13:23 +0000241 if flist:
242 flist.inversedict[self] = key
243 if key:
244 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000245 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000246 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
247 text.bind("<<open-class-browser>>", self.open_class_browser)
248 text.bind("<<open-path-browser>>", self.open_path_browser)
249
Steven M. Gava898a3652001-10-07 11:10:44 +0000250 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000251 vbar['command'] = text.yview
252 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000253 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000254 fontWeight = 'normal'
255 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000256 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000257 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200258 idleConf.GetOption('main', 'EditorWindow',
259 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000260 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000261 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
262 text.pack(side=TOP, fill=BOTH, expand=1)
263 text.focus_set()
264
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000265 # usetabs true -> literal tab characters are used by indent and
266 # dedent cmds, possibly mixed with spaces if
267 # indentwidth is not a multiple of tabwidth,
268 # which will cause Tabnanny to nag!
269 # false -> tab characters are converted to spaces by indent
270 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000271 # Although use-spaces=0 can be configured manually in config-main.def,
272 # configuration of tabs v. spaces is not supported in the configuration
273 # dialog. IDLE promotes the preferred Python indentation: use spaces!
274 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
275 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000276
277 # tabwidth is the display width of a literal tab character.
278 # CAUTION: telling Tk to use anything other than its default
279 # tab setting causes it to use an entirely different tabbing algorithm,
280 # treating tab stops as fixed distances from the left margin.
281 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000282 self.tabwidth = 8 # must remain 8 until Tk is fixed.
283
284 # indentwidth is the number of screen characters per indent level.
285 # The recommended Python indentation is four spaces.
286 self.indentwidth = self.tabwidth
287 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000288
289 # If context_use_ps1 is true, parsing searches back for a ps1 line;
290 # else searches for a popular (if, def, ...) Python stmt.
291 self.context_use_ps1 = False
292
293 # When searching backwards for a reliable place to begin parsing,
294 # first start num_context_lines[0] lines back, then
295 # num_context_lines[1] lines back if that didn't work, and so on.
296 # The last value should be huge (larger than the # of lines in a
297 # conceivable file).
298 # Making the initial values larger slows things down more often.
299 self.num_context_lines = 50, 500, 5000000
300
David Scherer7aced172000-08-15 01:13:23 +0000301 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000302
303 self.undo = undo = self.UndoDelegator()
304 per.insertfilter(undo)
305 text.undo_block_start = undo.undo_block_start
306 text.undo_block_stop = undo.undo_block_stop
307 undo.set_saved_change_hook(self.saved_change_hook)
308
309 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000310 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000311 io.set_filename_change_hook(self.filename_change_hook)
312
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000313 # Create the recent files submenu
314 self.recent_files_menu = Menu(self.menubar)
315 self.menudict['file'].insert_cascade(3, label='Recent Files',
316 underline=0,
317 menu=self.recent_files_menu)
318 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000319
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000320 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000321 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000322 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000323 io.loadfile(filename)
324 else:
325 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000326 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000327 self.saved_change_hook()
328
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000329 self.set_indentation_params(self.ispythonsource(filename))
330
David Scherer7aced172000-08-15 01:13:23 +0000331 self.load_extensions()
332
333 menu = self.menudict.get('windows')
334 if menu:
335 end = menu.index("end")
336 if end is None:
337 end = -1
338 if end >= 0:
339 menu.add_separator()
340 end = end + 1
341 self.wmenu_end = end
342 WindowList.register_callback(self.postwindowsmenu)
343
344 # Some abstractions so IDLE extensions are cross-IDE
345 self.askyesno = tkMessageBox.askyesno
346 self.askinteger = tkSimpleDialog.askinteger
347 self.showerror = tkMessageBox.showerror
348
Martin v. Löwis307021f2005-11-27 16:59:04 +0000349 def _filename_to_unicode(self, filename):
350 """convert filename to unicode in order to display it in Tk"""
351 if isinstance(filename, unicode) or not filename:
352 return filename
353 else:
354 try:
355 return filename.decode(self.filesystemencoding)
356 except UnicodeDecodeError:
357 # XXX
358 try:
359 return filename.decode(self.encoding)
360 except UnicodeDecodeError:
361 # byte-to-byte conversion
362 return filename.decode('iso8859-1')
363
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000364 def new_callback(self, event):
365 dirname, basename = self.io.defaultfilename()
366 self.flist.new(dirname)
367 return "break"
368
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000369 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400370 if (event.state & 4) != 0 and event.keysym == "Home":
371 # state&4==Control. If <Control-Home>, use the Tk binding.
372 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000373 if self.text.index("iomark") and \
374 self.text.compare("iomark", "<=", "insert lineend") and \
375 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400376 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000377 insertpt = int(self.text.index("iomark").split(".")[1])
378 else:
379 line = self.text.get("insert linestart", "insert lineend")
380 for insertpt in xrange(len(line)):
381 if line[insertpt] not in (' ','\t'):
382 break
383 else:
384 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000385 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000386 if insertpt == lineat:
387 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000388 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000389 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400390 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000391 self.text.tag_remove("sel", "1.0", "end")
392 else:
393 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400394 self.text.mark_set("my_anchor", "insert") # there was no previous selection
395 else:
396 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
397 self.text.mark_set("my_anchor", "sel.first") # extend back
398 else:
399 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000400 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400401 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000402 if self.text.compare(first,">",last):
403 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000404 self.text.tag_remove("sel", "1.0", "end")
405 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000406 self.text.mark_set("insert", dest)
407 self.text.see("insert")
408 return "break"
409
David Scherer7aced172000-08-15 01:13:23 +0000410 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000411 self.status_bar = self.MultiStatusBar(self.top)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000412 if macosxSupport.runningAsOSXApp():
413 # Insert some padding to avoid obscuring some of the statusbar
414 # by the resize widget.
415 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000416 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
417 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
418 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000419 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
420 self.text.event_add("<<set-line-and-column>>",
421 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000422 self.text.after_idle(self.set_line_and_column)
423
424 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000425 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000426 self.status_bar.set_label('column', 'Col: %s' % column)
427 self.status_bar.set_label('line', 'Ln: %s' % line)
428
David Scherer7aced172000-08-15 01:13:23 +0000429 menu_specs = [
430 ("file", "_File"),
431 ("edit", "_Edit"),
432 ("format", "F_ormat"),
433 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000434 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000435 ("windows", "_Windows"),
436 ("help", "_Help"),
437 ]
438
Ronald Oussoren19302d92006-06-11 14:33:36 +0000439 if macosxSupport.runningAsOSXApp():
440 del menu_specs[-3]
441 menu_specs[-2] = ("windows", "_Window")
442
443
David Scherer7aced172000-08-15 01:13:23 +0000444 def createmenubar(self):
445 mbar = self.menubar
446 self.menudict = menudict = {}
447 for name, label in self.menu_specs:
448 underline, label = prepstr(label)
449 menudict[name] = menu = Menu(mbar, name=name)
450 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000451
Ned Deily4a705502011-01-18 04:33:22 +0000452 if macosxSupport.isCarbonAquaTk(self.root):
Ronald Oussoren19302d92006-06-11 14:33:36 +0000453 # Insert the application menu
454 menudict['application'] = menu = Menu(mbar, name='apple')
455 mbar.add_cascade(label='IDLE', menu=menu)
456
David Scherer7aced172000-08-15 01:13:23 +0000457 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000458 self.base_helpmenu_length = self.menudict['help'].index(END)
459 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000460
461 def postwindowsmenu(self):
462 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000463 menu = self.menudict['windows']
464 end = menu.index("end")
465 if end is None:
466 end = -1
467 if end > self.wmenu_end:
468 menu.delete(self.wmenu_end+1, end)
469 WindowList.add_windows_to_menu(menu)
470
471 rmenu = None
472
473 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000474 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
475 if not self.rmenu:
476 self.make_rmenu()
477 rmenu = self.rmenu
478 self.event = event
479 iswin = sys.platform[:3] == 'win'
480 if iswin:
481 self.text.config(cursor="arrow")
Andrew Svetlov5018db72012-11-01 22:39:14 +0200482
Roger Serwy231a8fd2013-04-07 12:15:52 -0500483 for item in self.rmenu_specs:
484 try:
485 label, eventname, verify_state = item
486 except ValueError: # see issue1207589
487 continue
488
Andrew Svetlov5018db72012-11-01 22:39:14 +0200489 if verify_state is None:
490 continue
491 state = getattr(self, verify_state)()
492 rmenu.entryconfigure(label, state=state)
493
David Scherer7aced172000-08-15 01:13:23 +0000494 rmenu.tk_popup(event.x_root, event.y_root)
495 if iswin:
496 self.text.config(cursor="ibeam")
497
498 rmenu_specs = [
Andrew Svetlov5018db72012-11-01 22:39:14 +0200499 # ("Label", "<<virtual-event>>", "statefuncname"), ...
500 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000501 ]
502
503 def make_rmenu(self):
504 rmenu = Menu(self.text, tearoff=0)
Roger Serwy231a8fd2013-04-07 12:15:52 -0500505 for item in self.rmenu_specs:
506 label, eventname = item[0], item[1]
Andrew Svetlov5018db72012-11-01 22:39:14 +0200507 if label is not None:
508 def command(text=self.text, eventname=eventname):
509 text.event_generate(eventname)
510 rmenu.add_command(label=label, command=command)
511 else:
512 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000513 self.rmenu = rmenu
514
Andrew Svetlov5018db72012-11-01 22:39:14 +0200515 def rmenu_check_cut(self):
516 return self.rmenu_check_copy()
517
518 def rmenu_check_copy(self):
519 try:
520 indx = self.text.index('sel.first')
521 except TclError:
522 return 'disabled'
523 else:
524 return 'normal' if indx else 'disabled'
525
526 def rmenu_check_paste(self):
527 try:
528 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
529 except TclError:
530 return 'disabled'
531 else:
532 return 'normal'
533
David Scherer7aced172000-08-15 01:13:23 +0000534 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000535 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000536
Steven M. Gava3b55a892001-11-21 05:56:26 +0000537 def config_dialog(self, event=None):
538 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000539
David Scherer7aced172000-08-15 01:13:23 +0000540 def help_dialog(self, event=None):
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500541 if self.root:
542 parent = self.root
543 else:
544 parent = self.top
545 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000546
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000547 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000548 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000549 try:
550 os.startfile(self.help_url)
551 except WindowsError as why:
552 tkMessageBox.showerror(title='Document Start Failure',
553 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000554 else:
555 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000556 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000557
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000558 def cut(self,event):
559 self.text.event_generate("<<Cut>>")
560 return "break"
561
562 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000563 if not self.text.tag_ranges("sel"):
564 # There is no selection, so do nothing and maybe interrupt.
565 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000566 self.text.event_generate("<<Copy>>")
567 return "break"
568
569 def paste(self,event):
570 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000571 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000572 return "break"
573
David Scherer7aced172000-08-15 01:13:23 +0000574 def select_all(self, event=None):
575 self.text.tag_add("sel", "1.0", "end-1c")
576 self.text.mark_set("insert", "1.0")
577 self.text.see("insert")
578 return "break"
579
580 def remove_selection(self, event=None):
581 self.text.tag_remove("sel", "1.0", "end")
582 self.text.see("insert")
583
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000584 def move_at_edge_if_selection(self, edge_index):
585 """Cursor move begins at start or end of selection
586
587 When a left/right cursor key is pressed create and return to Tkinter a
588 function which causes a cursor move from the associated edge of the
589 selection.
590
591 """
592 self_text_index = self.text.index
593 self_text_mark_set = self.text.mark_set
594 edges_table = ("sel.first+1c", "sel.last-1c")
595 def move_at_edge(event):
596 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
597 try:
598 self_text_index("sel.first")
599 self_text_mark_set("insert", edges_table[edge_index])
600 except TclError:
601 pass
602 return move_at_edge
603
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000604 def del_word_left(self, event):
605 self.text.event_generate('<Meta-Delete>')
606 return "break"
607
608 def del_word_right(self, event):
609 self.text.event_generate('<Meta-d>')
610 return "break"
611
Steven M. Gavac5976402002-01-04 03:06:08 +0000612 def find_event(self, event):
613 SearchDialog.find(self.text)
614 return "break"
615
616 def find_again_event(self, event):
617 SearchDialog.find_again(self.text)
618 return "break"
619
620 def find_selection_event(self, event):
621 SearchDialog.find_selection(self.text)
622 return "break"
623
624 def find_in_files_event(self, event):
625 GrepDialog.grep(self.text, self.io, self.flist)
626 return "break"
627
628 def replace_event(self, event):
629 ReplaceDialog.replace(self.text)
630 return "break"
631
632 def goto_line_event(self, event):
633 text = self.text
634 lineno = tkSimpleDialog.askinteger("Goto",
635 "Go to line number:",parent=text)
636 if lineno is None:
637 return "break"
638 if lineno <= 0:
639 text.bell()
640 return "break"
641 text.mark_set("insert", "%d.0" % lineno)
642 text.see("insert")
643
David Scherer7aced172000-08-15 01:13:23 +0000644 def open_module(self, event=None):
645 # XXX Shouldn't this be in IOBinding or in FileList?
646 try:
647 name = self.text.get("sel.first", "sel.last")
648 except TclError:
649 name = ""
650 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000651 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000652 name = tkSimpleDialog.askstring("Module",
653 "Enter the name of a Python module\n"
654 "to search on sys.path and open:",
655 parent=self.text, initialvalue=name)
656 if name:
657 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000658 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000659 return
David Scherer7aced172000-08-15 01:13:23 +0000660 # XXX Ought to insert current file's directory in front of path
661 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000662 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000663 except (NameError, ImportError), msg:
664 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
665 return
666 if type != imp.PY_SOURCE:
667 tkMessageBox.showerror("Unsupported type",
668 "%s is not a source module" % name, parent=self.text)
669 return
670 if f:
671 f.close()
672 if self.flist:
673 self.flist.open(file)
674 else:
675 self.io.loadfile(file)
676
677 def open_class_browser(self, event=None):
678 filename = self.io.filename
679 if not filename:
680 tkMessageBox.showerror(
681 "No filename",
682 "This buffer has no associated filename",
683 master=self.text)
684 self.text.focus_set()
685 return None
686 head, tail = os.path.split(filename)
687 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000688 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000689 ClassBrowser.ClassBrowser(self.flist, base, [head])
690
691 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000692 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000693 PathBrowser.PathBrowser(self.flist)
694
695 def gotoline(self, lineno):
696 if lineno is not None and lineno > 0:
697 self.text.mark_set("insert", "%d.0" % lineno)
698 self.text.tag_remove("sel", "1.0", "end")
699 self.text.tag_add("sel", "insert", "insert +1l")
700 self.center()
701
702 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000703 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000704 return True
David Scherer7aced172000-08-15 01:13:23 +0000705 base, ext = os.path.splitext(os.path.basename(filename))
706 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000707 return True
David Scherer7aced172000-08-15 01:13:23 +0000708 try:
709 f = open(filename)
710 line = f.readline()
711 f.close()
712 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000713 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000714 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000715
716 def close_hook(self):
717 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000718 self.flist.unregister_maybe_terminate(self)
719 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000720
721 def set_close_hook(self, close_hook):
722 self.close_hook = close_hook
723
724 def filename_change_hook(self):
725 if self.flist:
726 self.flist.filename_changed_edit(self)
727 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000728 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000729 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000730
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000731 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000732 if self.color:
733 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000734 if self.ispythonsource(self.io.filename):
735 self.color = self.ColorDelegator()
736 # can add more colorizers here...
737 if self.color:
738 self.per.removefilter(self.undo)
739 self.per.insertfilter(self.color)
740 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000741
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000742 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000743 if not self.color:
744 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000745 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000746 self.per.removefilter(self.color)
747 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000748
Steven M. Gavab77d3432002-03-02 07:16:21 +0000749 def ResetColorizer(self):
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000750 "Update the colour theme"
751 # Called from self.filename_change_hook and from configDialog.py
752 self._rmcolorizer()
753 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000754 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000755 normal_colors = idleConf.GetHighlight(theme, 'normal')
756 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
757 select_colors = idleConf.GetHighlight(theme, 'hilite')
758 self.text.config(
759 foreground=normal_colors['foreground'],
760 background=normal_colors['background'],
761 insertbackground=cursor_color,
762 selectforeground=select_colors['foreground'],
763 selectbackground=select_colors['background'],
764 )
David Scherer7aced172000-08-15 01:13:23 +0000765
Steven M. Gavab1585412002-03-12 00:21:56 +0000766 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000767 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000768 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000769 fontWeight='normal'
770 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
771 fontWeight='bold'
772 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200773 idleConf.GetOption('main','EditorWindow','font-size',
774 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000775 fontWeight))
776
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000777 def RemoveKeybindings(self):
778 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000779 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000780 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000781 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000782 self.text.event_delete(event, *keylist)
783 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000784 xkeydefs = idleConf.GetExtensionBindings(extensionName)
785 if xkeydefs:
786 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000787 self.text.event_delete(event, *keylist)
788
789 def ApplyKeybindings(self):
790 "Update the keybindings after they are changed"
791 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000792 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000793 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000794 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000795 xkeydefs = idleConf.GetExtensionBindings(extensionName)
796 if xkeydefs:
797 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000798 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000799 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000800 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000801 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000802 for item in menu[1]:
803 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000804 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000805 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000806 menu = self.menudict[menubarItem]
807 end = menu.index(END) + 1
808 for index in range(0, end):
809 if menu.type(index) == 'command':
810 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000811 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000812 itemName = menu.entrycget(index, 'label')
813 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000814 if menubarItem in menuEventDict:
815 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000816 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000817 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000818 accel = get_accelerator(keydefs, event)
819 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000820
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000821 def set_notabs_indentwidth(self):
822 "Update the indentwidth if changed and not using tabs in this window"
823 # Called from configDialog.py
824 if not self.usetabs:
825 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
826 type='int')
827
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000828 def reset_help_menu_entries(self):
829 "Update the additional help entries on the Help menu"
830 help_list = idleConf.GetAllExtraHelpSourcesList()
831 helpmenu = self.menudict['help']
832 # first delete the extra help entries, if any
833 helpmenu_length = helpmenu.index(END)
834 if helpmenu_length > self.base_helpmenu_length:
835 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
836 # then rebuild them
837 if help_list:
838 helpmenu.add_separator()
839 for entry in help_list:
840 cmd = self.__extra_help_callback(entry[1])
841 helpmenu.add_command(label=entry[0], command=cmd)
842 # and update the menu dictionary
843 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000844
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000845 def __extra_help_callback(self, helpfile):
846 "Create a callback with the helpfile value frozen at definition time"
847 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000848 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000849 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000850 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000851 try:
852 os.startfile(helpfile)
853 except WindowsError as why:
854 tkMessageBox.showerror(title='Document Start Failure',
855 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000856 else:
857 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000858 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000859
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000860 def update_recent_files_list(self, new_file=None):
861 "Load and update the recent files list and menus"
862 rf_list = []
863 if os.path.exists(self.recent_files_path):
864 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000865 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000866 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000867 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000868 rf_list_file.close()
869 if new_file:
870 new_file = os.path.abspath(new_file) + '\n'
871 if new_file in rf_list:
872 rf_list.remove(new_file) # move to top
873 rf_list.insert(0, new_file)
874 # clean and save the recent files list
875 bad_paths = []
876 for path in rf_list:
877 if '\0' in path or not os.path.exists(path[0:-1]):
878 bad_paths.append(path)
879 rf_list = [path for path in rf_list if path not in bad_paths]
880 ulchars = "1234567890ABCDEFGHIJK"
881 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000882 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800883 with open(self.recent_files_path, 'w') as rf_file:
884 rf_file.writelines(rf_list)
885 except IOError as err:
886 if not getattr(self.root, "recentfilelist_error_displayed", False):
887 self.root.recentfilelist_error_displayed = True
888 tkMessageBox.showerror(title='IDLE Error',
889 message='Unable to update Recent Files list:\n%s'
890 % str(err),
891 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000892 # for each edit window instance, construct the recent files menu
893 for instance in self.top.instance_dict.keys():
894 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700895 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000896 for i, file_name in enumerate(rf_list):
897 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000898 # make unicode string to display non-ASCII chars correctly
899 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000900 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000901 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000902 command=callback,
903 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000904
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000905 def __recent_file_callback(self, file_name):
906 def open_recent_file(fn_closure=file_name):
907 self.io.open(editFile=fn_closure)
908 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000909
David Scherer7aced172000-08-15 01:13:23 +0000910 def saved_change_hook(self):
911 short = self.short_title()
912 long = self.long_title()
913 if short and long:
914 title = short + " - " + long
915 elif short:
916 title = short
917 elif long:
918 title = long
919 else:
920 title = "Untitled"
921 icon = short or long or title
922 if not self.get_saved():
923 title = "*%s*" % title
924 icon = "*%s" % icon
925 self.top.wm_title(title)
926 self.top.wm_iconname(icon)
927
928 def get_saved(self):
929 return self.undo.get_saved()
930
931 def set_saved(self, flag):
932 self.undo.set_saved(flag)
933
934 def reset_undo(self):
935 self.undo.reset_undo()
936
937 def short_title(self):
938 filename = self.io.filename
939 if filename:
940 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000941 # return unicode string to display non-ASCII chars correctly
942 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000943
944 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000945 # return unicode string to display non-ASCII chars correctly
946 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000947
948 def center_insert_event(self, event):
949 self.center()
950
951 def center(self, mark="insert"):
952 text = self.text
953 top, bot = self.getwindowlines()
954 lineno = self.getlineno(mark)
955 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000956 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000957 text.yview(float(newtop))
958
959 def getwindowlines(self):
960 text = self.text
961 top = self.getlineno("@0,0")
962 bot = self.getlineno("@0,65535")
963 if top == bot and text.winfo_height() == 1:
964 # Geometry manager hasn't run yet
965 height = int(text['height'])
966 bot = top + height - 1
967 return top, bot
968
969 def getlineno(self, mark="insert"):
970 text = self.text
971 return int(float(text.index(mark)))
972
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000973 def get_geometry(self):
974 "Return (width, height, x, y)"
975 geom = self.top.wm_geometry()
976 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
977 tuple = (map(int, m.groups()))
978 return tuple
979
David Scherer7aced172000-08-15 01:13:23 +0000980 def close_event(self, event):
981 self.close()
982
983 def maybesave(self):
984 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000985 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000986 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000987 self.top.deiconify()
988 self.top.lower()
989 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000990 return self.io.maybesave()
991
992 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000993 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +0000994 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000995 self._close()
996 return reply
997
998 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000999 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001000 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001001 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001002 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001003 self.io.close()
1004 self.io = None
1005 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001006 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001007 self.color.close(False)
1008 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001009 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001010 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001011 self.per.close()
1012 self.per = None
1013 self.top.destroy()
1014 if self.close_hook:
1015 # unless override: unregister from flist, terminate if last window
1016 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001017
1018 def load_extensions(self):
1019 self.extensions = {}
1020 self.load_standard_extensions()
1021
1022 def unload_extensions(self):
1023 for ins in self.extensions.values():
1024 if hasattr(ins, "close"):
1025 ins.close()
1026 self.extensions = {}
1027
1028 def load_standard_extensions(self):
1029 for name in self.get_standard_extension_names():
1030 try:
1031 self.load_extension(name)
1032 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001033 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001034 import traceback
1035 traceback.print_exc()
1036
1037 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001038 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001039
1040 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001041 try:
1042 mod = __import__(name, globals(), locals(), [])
1043 except ImportError:
1044 print "\nFailed to import extension: ", name
1045 return
David Scherer7aced172000-08-15 01:13:23 +00001046 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001047 keydefs = idleConf.GetExtensionBindings(name)
1048 if hasattr(cls, "menudefs"):
1049 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001050 ins = cls(self)
1051 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001052 if keydefs:
1053 self.apply_bindings(keydefs)
1054 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001055 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001056 while methodname[:1] == '<':
1057 methodname = methodname[1:]
1058 while methodname[-1:] == '>':
1059 methodname = methodname[:-1]
1060 methodname = methodname + "_event"
1061 if hasattr(ins, methodname):
1062 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001063
1064 def apply_bindings(self, keydefs=None):
1065 if keydefs is None:
1066 keydefs = self.Bindings.default_keydefs
1067 text = self.text
1068 text.keydefs = keydefs
1069 for event, keylist in keydefs.items():
1070 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001071 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001072
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001073 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001074 """Add appropriate entries to the menus and submenus
1075
1076 Menus that are absent or None in self.menudict are ignored.
1077 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001078 if menudefs is None:
1079 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001080 if keydefs is None:
1081 keydefs = self.Bindings.default_keydefs
1082 menudict = self.menudict
1083 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001084 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001085 menu = menudict.get(mname)
1086 if not menu:
1087 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001088 for entry in entrylist:
1089 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001090 menu.add_separator()
1091 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001092 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001093 checkbutton = (label[:1] == '!')
1094 if checkbutton:
1095 label = label[1:]
1096 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001097 accelerator = get_accelerator(keydefs, eventname)
1098 def command(text=text, eventname=eventname):
1099 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001100 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001101 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001102 menu.add_checkbutton(label=label, underline=underline,
1103 command=command, accelerator=accelerator,
1104 variable=var)
1105 else:
1106 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001107 command=command,
1108 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001109
1110 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001111 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001112 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001113 value = var.get()
1114 return value
1115 else:
1116 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001117
1118 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001119 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001120 if var:
1121 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001122 else:
1123 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001124
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001125 def get_var_obj(self, name, vartype=None):
1126 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001127 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001128 # create a Tkinter variable object with self.text as master:
1129 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001130 return var
1131
1132 # Tk implementations of "virtual text methods" -- each platform
1133 # reusing IDLE's support code needs to define these for its GUI's
1134 # flavor of widget.
1135
1136 # Is character at text_index in a Python string? Return 0 for
1137 # "guaranteed no", true for anything else. This info is expensive
1138 # to compute ab initio, but is probably already known by the
1139 # platform's colorizer.
1140
1141 def is_char_in_string(self, text_index):
1142 if self.color:
1143 # Return true iff colorizer hasn't (re)gotten this far
1144 # yet, or the character is tagged as being in a string
1145 return self.text.tag_prevrange("TODO", text_index) or \
1146 "STRING" in self.text.tag_names(text_index)
1147 else:
1148 # The colorizer is missing: assume the worst
1149 return 1
1150
1151 # If a selection is defined in the text widget, return (start,
1152 # end) as Tkinter text indices, otherwise return (None, None)
1153 def get_selection_indices(self):
1154 try:
1155 first = self.text.index("sel.first")
1156 last = self.text.index("sel.last")
1157 return first, last
1158 except TclError:
1159 return None, None
1160
1161 # Return the text widget's current view of what a tab stop means
1162 # (equivalent width in spaces).
1163
1164 def get_tabwidth(self):
1165 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1166 return int(current)
1167
1168 # Set the text widget's current view of what a tab stop means.
1169
1170 def set_tabwidth(self, newtabwidth):
1171 text = self.text
1172 if self.get_tabwidth() != newtabwidth:
1173 pixels = text.tk.call("font", "measure", text["font"],
1174 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001175 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001176 text.configure(tabs=pixels)
1177
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001178 # If ispythonsource and guess are true, guess a good value for
1179 # indentwidth based on file content (if possible), and if
1180 # indentwidth != tabwidth set usetabs false.
1181 # In any case, adjust the Text widget's view of what a tab
1182 # character means.
1183
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001184 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001185 if guess and ispythonsource:
1186 i = self.guess_indent()
1187 if 2 <= i <= 8:
1188 self.indentwidth = i
1189 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001190 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001191 self.set_tabwidth(self.tabwidth)
1192
1193 def smart_backspace_event(self, event):
1194 text = self.text
1195 first, last = self.get_selection_indices()
1196 if first and last:
1197 text.delete(first, last)
1198 text.mark_set("insert", first)
1199 return "break"
1200 # Delete whitespace left, until hitting a real char or closest
1201 # preceding virtual tab stop.
1202 chars = text.get("insert linestart", "insert")
1203 if chars == '':
1204 if text.compare("insert", ">", "1.0"):
1205 # easy: delete preceding newline
1206 text.delete("insert-1c")
1207 else:
1208 text.bell() # at start of buffer
1209 return "break"
1210 if chars[-1] not in " \t":
1211 # easy: delete preceding real char
1212 text.delete("insert-1c")
1213 return "break"
1214 # Ick. It may require *inserting* spaces if we back up over a
1215 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001216 tabwidth = self.tabwidth
1217 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001218 assert have > 0
1219 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001220 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001221 if self.context_use_ps1:
1222 last_line_of_prompt = sys.ps1.split('\n')[-1]
1223 else:
1224 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001225 ncharsdeleted = 0
1226 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001227 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001228 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001229 chars = chars[:-1]
1230 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001231 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001232 if have <= want or chars[-1] not in " \t":
1233 break
1234 text.undo_block_start()
1235 text.delete("insert-%dc" % ncharsdeleted, "insert")
1236 if have < want:
1237 text.insert("insert", ' ' * (want - have))
1238 text.undo_block_stop()
1239 return "break"
1240
1241 def smart_indent_event(self, event):
1242 # if intraline selection:
1243 # delete it
1244 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001245 # do indent-region
1246 # else:
1247 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001248 text = self.text
1249 first, last = self.get_selection_indices()
1250 text.undo_block_start()
1251 try:
1252 if first and last:
1253 if index2line(first) != index2line(last):
1254 return self.indent_region_event(event)
1255 text.delete(first, last)
1256 text.mark_set("insert", first)
1257 prefix = text.get("insert linestart", "insert")
1258 raw, effective = classifyws(prefix, self.tabwidth)
1259 if raw == len(prefix):
1260 # only whitespace to the left
1261 self.reindent_to(effective + self.indentwidth)
1262 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001263 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001264 if self.usetabs:
1265 pad = '\t'
1266 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001267 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001268 n = self.indentwidth
1269 pad = ' ' * (n - effective % n)
1270 text.insert("insert", pad)
1271 text.see("insert")
1272 return "break"
1273 finally:
1274 text.undo_block_stop()
1275
1276 def newline_and_indent_event(self, event):
1277 text = self.text
1278 first, last = self.get_selection_indices()
1279 text.undo_block_start()
1280 try:
1281 if first and last:
1282 text.delete(first, last)
1283 text.mark_set("insert", first)
1284 line = text.get("insert linestart", "insert")
1285 i, n = 0, len(line)
1286 while i < n and line[i] in " \t":
1287 i = i+1
1288 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001289 # the cursor is in or at leading indentation in a continuation
1290 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001291 text.insert("insert linestart", '\n')
1292 return "break"
1293 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001294 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001295 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001296 last_line_of_prompt = sys.ps1.split('\n')[-1]
1297 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001298 line = line[:-1]
1299 i = i+1
1300 if i:
1301 text.delete("insert - %d chars" % i, "insert")
1302 # strip whitespace after insert point
1303 while text.get("insert") in " \t":
1304 text.delete("insert")
1305 # start new line
1306 text.insert("insert", '\n')
1307
1308 # adjust indentation for continuations and block
1309 # open/close first need to find the last stmt
1310 lno = index2line(text.index('insert'))
1311 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001312 if not self.context_use_ps1:
1313 for context in self.num_context_lines:
1314 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001315 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001316 rawtext = text.get(startatindex, "insert")
1317 y.set_str(rawtext)
1318 bod = y.find_good_parse_start(
1319 self.context_use_ps1,
1320 self._build_char_in_string_func(startatindex))
1321 if bod is not None or startat == 1:
1322 break
1323 y.set_lo(bod or 0)
1324 else:
1325 r = text.tag_prevrange("console", "insert")
1326 if r:
1327 startatindex = r[1]
1328 else:
1329 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001330 rawtext = text.get(startatindex, "insert")
1331 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001332 y.set_lo(0)
1333
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001334 c = y.get_continuation_type()
1335 if c != PyParse.C_NONE:
1336 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001337 if c == PyParse.C_STRING_FIRST_LINE:
1338 # after the first line of a string; do not indent at all
1339 pass
1340 elif c == PyParse.C_STRING_NEXT_LINES:
1341 # inside a string which started before this line;
1342 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001343 text.insert("insert", indent)
1344 elif c == PyParse.C_BRACKET:
1345 # line up with the first (if any) element of the
1346 # last open bracket structure; else indent one
1347 # level beyond the indent of the line with the
1348 # last open bracket
1349 self.reindent_to(y.compute_bracket_indent())
1350 elif c == PyParse.C_BACKSLASH:
1351 # if more than one line in this stmt already, just
1352 # mimic the current indent; else if initial line
1353 # has a start on an assignment stmt, indent to
1354 # beyond leftmost =; else to beyond first chunk of
1355 # non-whitespace on initial line
1356 if y.get_num_lines_in_stmt() > 1:
1357 text.insert("insert", indent)
1358 else:
1359 self.reindent_to(y.compute_backslash_indent())
1360 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001361 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001362 return "break"
1363
1364 # This line starts a brand new stmt; indent relative to
1365 # indentation of initial line of closest preceding
1366 # interesting stmt.
1367 indent = y.get_base_indent_string()
1368 text.insert("insert", indent)
1369 if y.is_block_opener():
1370 self.smart_indent_event(event)
1371 elif indent and y.is_block_closer():
1372 self.smart_backspace_event(event)
1373 return "break"
1374 finally:
1375 text.see("insert")
1376 text.undo_block_stop()
1377
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001378 # Our editwin provides a is_char_in_string function that works
1379 # with a Tk text index, but PyParse only knows about offsets into
1380 # a string. This builds a function for PyParse that accepts an
1381 # offset.
1382
1383 def _build_char_in_string_func(self, startindex):
1384 def inner(offset, _startindex=startindex,
1385 _icis=self.is_char_in_string):
1386 return _icis(_startindex + "+%dc" % offset)
1387 return inner
1388
1389 def indent_region_event(self, event):
1390 head, tail, chars, lines = self.get_region()
1391 for pos in range(len(lines)):
1392 line = lines[pos]
1393 if line:
1394 raw, effective = classifyws(line, self.tabwidth)
1395 effective = effective + self.indentwidth
1396 lines[pos] = self._make_blanks(effective) + line[raw:]
1397 self.set_region(head, tail, chars, lines)
1398 return "break"
1399
1400 def dedent_region_event(self, event):
1401 head, tail, chars, lines = self.get_region()
1402 for pos in range(len(lines)):
1403 line = lines[pos]
1404 if line:
1405 raw, effective = classifyws(line, self.tabwidth)
1406 effective = max(effective - self.indentwidth, 0)
1407 lines[pos] = self._make_blanks(effective) + line[raw:]
1408 self.set_region(head, tail, chars, lines)
1409 return "break"
1410
1411 def comment_region_event(self, event):
1412 head, tail, chars, lines = self.get_region()
1413 for pos in range(len(lines) - 1):
1414 line = lines[pos]
1415 lines[pos] = '##' + line
1416 self.set_region(head, tail, chars, lines)
1417
1418 def uncomment_region_event(self, event):
1419 head, tail, chars, lines = self.get_region()
1420 for pos in range(len(lines)):
1421 line = lines[pos]
1422 if not line:
1423 continue
1424 if line[:2] == '##':
1425 line = line[2:]
1426 elif line[:1] == '#':
1427 line = line[1:]
1428 lines[pos] = line
1429 self.set_region(head, tail, chars, lines)
1430
1431 def tabify_region_event(self, event):
1432 head, tail, chars, lines = self.get_region()
1433 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001434 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001435 for pos in range(len(lines)):
1436 line = lines[pos]
1437 if line:
1438 raw, effective = classifyws(line, tabwidth)
1439 ntabs, nspaces = divmod(effective, tabwidth)
1440 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1441 self.set_region(head, tail, chars, lines)
1442
1443 def untabify_region_event(self, event):
1444 head, tail, chars, lines = self.get_region()
1445 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001446 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001447 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001448 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001449 self.set_region(head, tail, chars, lines)
1450
1451 def toggle_tabs_event(self, event):
1452 if self.askyesno(
1453 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001454 "Turn tabs " + ("on", "off")[self.usetabs] +
1455 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001456 ("will be", "remains at")[self.usetabs] + " 8." +
1457 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001458 parent=self.text):
1459 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001460 # Try to prevent inconsistent indentation.
1461 # User must change indent width manually after using tabs.
1462 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001463 return "break"
1464
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001465 # XXX this isn't bound to anything -- see tabwidth comments
1466## def change_tabwidth_event(self, event):
1467## new = self._asktabwidth()
1468## if new != self.tabwidth:
1469## self.tabwidth = new
1470## self.set_indentation_params(0, guess=0)
1471## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001472
1473 def change_indentwidth_event(self, event):
1474 new = self.askinteger(
1475 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001476 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001477 parent=self.text,
1478 initialvalue=self.indentwidth,
1479 minvalue=2,
1480 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001481 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001482 self.indentwidth = new
1483 return "break"
1484
1485 def get_region(self):
1486 text = self.text
1487 first, last = self.get_selection_indices()
1488 if first and last:
1489 head = text.index(first + " linestart")
1490 tail = text.index(last + "-1c lineend +1c")
1491 else:
1492 head = text.index("insert linestart")
1493 tail = text.index("insert lineend +1c")
1494 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001495 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001496 return head, tail, chars, lines
1497
1498 def set_region(self, head, tail, chars, lines):
1499 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001500 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001501 if newchars == chars:
1502 text.bell()
1503 return
1504 text.tag_remove("sel", "1.0", "end")
1505 text.mark_set("insert", head)
1506 text.undo_block_start()
1507 text.delete(head, tail)
1508 text.insert(head, newchars)
1509 text.undo_block_stop()
1510 text.tag_add("sel", head, "insert")
1511
1512 # Make string that displays as n leading blanks.
1513
1514 def _make_blanks(self, n):
1515 if self.usetabs:
1516 ntabs, nspaces = divmod(n, self.tabwidth)
1517 return '\t' * ntabs + ' ' * nspaces
1518 else:
1519 return ' ' * n
1520
1521 # Delete from beginning of line to insert point, then reinsert
1522 # column logical (meaning use tabs if appropriate) spaces.
1523
1524 def reindent_to(self, column):
1525 text = self.text
1526 text.undo_block_start()
1527 if text.compare("insert linestart", "!=", "insert"):
1528 text.delete("insert linestart", "insert")
1529 if column:
1530 text.insert("insert", self._make_blanks(column))
1531 text.undo_block_stop()
1532
1533 def _asktabwidth(self):
1534 return self.askinteger(
1535 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001536 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001537 parent=self.text,
1538 initialvalue=self.indentwidth,
1539 minvalue=2,
Roger Serwy75b249c2013-04-06 20:26:53 -05001540 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001541
1542 # Guess indentwidth from text content.
1543 # Return guessed indentwidth. This should not be believed unless
1544 # it's in a reasonable range (e.g., it will be 0 if no indented
1545 # blocks are found).
1546
1547 def guess_indent(self):
1548 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1549 if opener and indented:
1550 raw, indentsmall = classifyws(opener, self.tabwidth)
1551 raw, indentlarge = classifyws(indented, self.tabwidth)
1552 else:
1553 indentsmall = indentlarge = 0
1554 return indentlarge - indentsmall
1555
1556# "line.col" -> line, as an int
1557def index2line(index):
1558 return int(float(index))
1559
1560# Look at the leading whitespace in s.
1561# Return pair (# of leading ws characters,
1562# effective # of leading blanks after expanding
1563# tabs to width tabwidth)
1564
1565def classifyws(s, tabwidth):
1566 raw = effective = 0
1567 for ch in s:
1568 if ch == ' ':
1569 raw = raw + 1
1570 effective = effective + 1
1571 elif ch == '\t':
1572 raw = raw + 1
1573 effective = (effective // tabwidth + 1) * tabwidth
1574 else:
1575 break
1576 return raw, effective
1577
1578import tokenize
1579_tokenize = tokenize
1580del tokenize
1581
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001582class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001583
1584 # .run() chews over the Text widget, looking for a block opener
1585 # and the stmt following it. Returns a pair,
1586 # (line containing block opener, line containing stmt)
1587 # Either or both may be None.
1588
1589 def __init__(self, text, tabwidth):
1590 self.text = text
1591 self.tabwidth = tabwidth
1592 self.i = self.finished = 0
1593 self.blkopenline = self.indentedline = None
1594
1595 def readline(self):
1596 if self.finished:
1597 return ""
1598 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001599 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001600 if self.text.compare(mark, ">=", "end"):
1601 return ""
1602 return self.text.get(mark, mark + " lineend+1c")
1603
1604 def tokeneater(self, type, token, start, end, line,
1605 INDENT=_tokenize.INDENT,
1606 NAME=_tokenize.NAME,
1607 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1608 if self.finished:
1609 pass
1610 elif type == NAME and token in OPENERS:
1611 self.blkopenline = line
1612 elif type == INDENT and self.blkopenline:
1613 self.indentedline = line
1614 self.finished = 1
1615
1616 def run(self):
1617 save_tabsize = _tokenize.tabsize
1618 _tokenize.tabsize = self.tabwidth
1619 try:
1620 try:
1621 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001622 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001623 # since we cut off the tokenizer early, we can trigger
1624 # spurious errors
1625 pass
1626 finally:
1627 _tokenize.tabsize = save_tabsize
1628 return self.blkopenline, self.indentedline
1629
1630### end autoindent code ###
1631
David Scherer7aced172000-08-15 01:13:23 +00001632def prepstr(s):
1633 # Helper to extract the underscore from a string, e.g.
1634 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001635 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001636 if i >= 0:
1637 s = s[:i] + s[i+1:]
1638 return i, s
1639
1640
1641keynames = {
1642 'bracketleft': '[',
1643 'bracketright': ']',
1644 'slash': '/',
1645}
1646
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001647def get_accelerator(keydefs, eventname):
1648 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001649 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1650 # if not keylist:
1651 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1652 "<<open-module>>",
1653 "<<goto-line>>",
1654 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001655 return ""
1656 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001657 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001658 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1659 s = re.sub("Key-", "", s)
1660 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1661 s = re.sub("Control-", "Ctrl-", s)
1662 s = re.sub("-", "+", s)
1663 s = re.sub("><", " ", s)
1664 s = re.sub("<", "", s)
1665 s = re.sub(">", "", s)
1666 return s
1667
1668
1669def fixwordbreaks(root):
1670 # Make sure that Tk's double-click and next/previous word
1671 # operations use our definition of a word (i.e. an identifier)
1672 tk = root.tk
1673 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1674 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1675 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1676
1677
1678def test():
1679 root = Tk()
1680 fixwordbreaks(root)
1681 root.withdraw()
1682 if sys.argv[1:]:
1683 filename = sys.argv[1]
1684 else:
1685 filename = None
1686 edit = EditorWindow(root=root, filename=filename)
1687 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001688 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001689 root.mainloop()
1690 root.destroy()
1691
1692if __name__ == '__main__':
1693 test()