blob: 1243b95417a32d9b51d816299387bb645bfa53a3 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
Terry Jan Reedyc11633e2014-08-14 21:54:38 -04003import platform
David Scherer7aced172000-08-15 01:13:23 +00004import re
5import imp
Georg Brandl6634bf22008-05-20 07:13:37 +00006from Tkinter import *
7import tkSimpleDialog
8import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00009import webbrowser
Florent Xiclunad630c042010-04-02 07:24:52 +000010
11from idlelib.MultiCall import MultiCallCreator
Florent Xiclunad630c042010-04-02 07:24:52 +000012from 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
Terry Jan Reedy70e763c2015-09-20 19:55:44 -040020from idlelib import help
David Scherer7aced172000-08-15 01:13:23 +000021
22# The default tab setting for a Text widget, in average-width characters.
23TK_TABWIDTH_DEFAULT = 8
24
Terry Jan Reedyc11633e2014-08-14 21:54:38 -040025_py_version = ' (%s)' % platform.python_version()
26
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000027def _sphinx_version():
28 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
29 major, minor, micro, level, serial = sys.version_info
30 release = '%s%s' % (major, minor)
31 if micro:
Benjamin Petersonfb234632009-06-13 15:42:23 +000032 release += '%s' % (micro,)
33 if level == 'candidate':
34 release += 'rc%s' % (serial,)
35 elif level != 'final':
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000036 release += '%s%s' % (level[0], serial)
37 return release
38
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000039def _find_module(fullname, path=None):
40 """Version of imp.find_module() that handles hierarchical module names"""
41
42 file = None
43 for tgt in fullname.split('.'):
44 if file is not None:
45 file.close() # close intermediate files
46 (file, filename, descr) = imp.find_module(tgt, path)
47 if descr[2] == imp.PY_SOURCE:
48 break # find but not load the source file
49 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000050 try:
51 path = module.__path__
52 except AttributeError:
53 raise ImportError, 'No source for module ' + module.__name__
Raymond Hettinger179816d2011-04-12 18:54:46 -070054 if descr[2] != imp.PY_SOURCE:
55 # If all of the above fails and didn't raise an exception,fallback
56 # to a straight import which can find __init__.py in a package.
57 m = __import__(fullname)
58 try:
59 filename = m.__file__
60 except AttributeError:
61 pass
62 else:
63 file = None
64 base, ext = os.path.splitext(filename)
65 if ext == '.pyc':
66 ext = '.py'
67 filename = base + ext
68 descr = filename, None, imp.PY_SOURCE
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000069 return file, filename, descr
70
Terry Jan Reedyadb87e22012-02-05 15:10:55 -050071
72class HelpDialog(object):
73
74 def __init__(self):
75 self.parent = None # parent of help window
76 self.dlg = None # the help window iteself
77
78 def display(self, parent, near=None):
79 """ Display the help dialog.
80
81 parent - parent widget for the help window
82
83 near - a Toplevel widget (e.g. EditorWindow or PyShell)
84 to use as a reference for placing the help window
85 """
Terry Jan Reedyfea7fc12015-09-22 22:59:35 -040086 import warnings as w
87 w.warn("EditorWindow.HelpDialog is no longer used by Idle.\n"
88 "It will be removed in 3.6 or later.\n"
89 "It has been replaced by private help.HelpWindow\n",
90 DeprecationWarning, stacklevel=2)
Terry Jan Reedyadb87e22012-02-05 15:10:55 -050091 if self.dlg is None:
92 self.show_dialog(parent)
93 if near:
94 self.nearwindow(near)
95
96 def show_dialog(self, parent):
97 self.parent = parent
98 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
99 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
100 dlg.bind('<Destroy>', self.destroy, '+')
101
102 def nearwindow(self, near):
103 # Place the help dialog near the window specified by parent.
104 # Note - this may not reposition the window in Metacity
105 # if "/apps/metacity/general/disable_workarounds" is enabled
106 dlg = self.dlg
107 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
108 dlg.withdraw()
109 dlg.geometry("=+%d+%d" % geom)
110 dlg.deiconify()
111 dlg.lift()
112
113 def destroy(self, ev=None):
114 self.dlg = None
115 self.parent = None
116
Terry Jan Reedy8b7122c2015-09-20 23:05:21 -0400117helpDialog = HelpDialog() # singleton instance, no longer used
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500118
119
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000120class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +0000121 from idlelib.Percolator import Percolator
122 from idlelib.ColorDelegator import ColorDelegator
123 from idlelib.UndoDelegator import UndoDelegator
124 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
125 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +0000126 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +0000127 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000128
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000129 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000130
131 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000132 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +0000133 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000134 if sys.platform.count('linux'):
135 # look for html docs in a couple of standard places
136 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
137 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
138 dochome = '/var/www/html/python/index.html'
139 else:
140 basepath = '/usr/share/doc/' # standard location
141 dochome = os.path.join(basepath, pyver,
142 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000143 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +0000144 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +0000145 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000146 if os.path.isfile(chmfile):
147 dochome = chmfile
Ned Deily57847df2014-03-27 20:47:04 -0700148 elif sys.platform == 'darwin':
149 # documentation may be stored inside a python framework
Ronald Oussoren19302d92006-06-11 14:33:36 +0000150 dochome = os.path.join(sys.prefix,
151 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000152 dochome = os.path.normpath(dochome)
153 if os.path.isfile(dochome):
154 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +0000155 if sys.platform == 'darwin':
156 # Safari requires real file:-URLs
157 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000158 else:
Terry Jan Reedyb17c1e02014-09-19 22:54:09 -0400159 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
David Scherer7aced172000-08-15 01:13:23 +0000160 self.flist = flist
161 root = root or flist.root
162 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +0000163 try:
164 sys.ps1
165 except AttributeError:
166 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000167 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000168 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000169 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000170 self.tkinter_vars = flist.vars
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200171 #self.top.instance_dict makes flist.inversedict available to
172 #configDialog.py so it can access all EditorWindow instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000173 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000174 else:
175 self.tkinter_vars = {} # keys: Tkinter event names
176 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000177 self.top.instance_dict = {}
178 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000179 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000180 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000181 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200182 self.width = idleConf.GetOption('main','EditorWindow','width', type='int')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000183 text_options = {
184 'name': 'text',
185 'padx': 5,
186 'wrap': 'none',
187 'width': self.width,
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200188 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')}
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000189 if TkVersion >= 8.5:
190 # Starting with tk 8.5 we have to set the new tabstyle option
191 # to 'wordprocessor' to achieve the same display of tabs as in
192 # older tk versions.
193 text_options['tabstyle'] = 'wordprocessor'
194 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000195 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000196
197 self.createmenubar()
198 self.apply_bindings()
199
200 self.top.protocol("WM_DELETE_WINDOW", self.close)
201 self.top.bind("<<close-window>>", self.close_event)
Ned Deily57847df2014-03-27 20:47:04 -0700202 if macosxSupport.isAquaTk():
Ronald Oussoren17db4952006-07-23 09:41:09 +0000203 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000204 text.bind('<<close-window>>', self.close_event)
Terry Jan Reedyca33d562015-09-22 21:10:22 -0400205 # Some OS X systems have only one mouse button, so use
206 # control-click for popup context menus there. For two
207 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murray3f752ab2010-12-18 17:22:18 +0000208 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedyca33d562015-09-22 21:10:22 -0400209 text.bind("<2>", self.right_menu_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000210 else:
Terry Jan Reedyca33d562015-09-22 21:10:22 -0400211 # Elsewhere, use right-click for popup menus.
R. David Murray3f752ab2010-12-18 17:22:18 +0000212 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000213 text.bind("<<cut>>", self.cut)
214 text.bind("<<copy>>", self.copy)
215 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000216 text.bind("<<center-insert>>", self.center_insert_event)
217 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000218 text.bind("<<python-docs>>", self.python_docs)
219 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000220 text.bind("<<open-config-dialog>>", self.config_dialog)
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400221 text.bind("<<open-config-extensions-dialog>>",
222 self.config_extensions_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000223 text.bind("<<open-module>>", self.open_module)
224 text.bind("<<do-nothing>>", lambda event: "break")
225 text.bind("<<select-all>>", self.select_all)
226 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000227 text.bind("<<find>>", self.find_event)
228 text.bind("<<find-again>>", self.find_again_event)
229 text.bind("<<find-in-files>>", self.find_in_files_event)
230 text.bind("<<find-selection>>", self.find_selection_event)
231 text.bind("<<replace>>", self.replace_event)
232 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000233 text.bind("<<smart-backspace>>",self.smart_backspace_event)
234 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
235 text.bind("<<smart-indent>>",self.smart_indent_event)
236 text.bind("<<indent-region>>",self.indent_region_event)
237 text.bind("<<dedent-region>>",self.dedent_region_event)
238 text.bind("<<comment-region>>",self.comment_region_event)
239 text.bind("<<uncomment-region>>",self.uncomment_region_event)
240 text.bind("<<tabify-region>>",self.tabify_region_event)
241 text.bind("<<untabify-region>>",self.untabify_region_event)
242 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
243 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000244 text.bind("<Left>", self.move_at_edge_if_selection(0))
245 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000246 text.bind("<<del-word-left>>", self.del_word_left)
247 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000248 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000249
David Scherer7aced172000-08-15 01:13:23 +0000250 if flist:
251 flist.inversedict[self] = key
252 if key:
253 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000254 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000255 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
256 text.bind("<<open-class-browser>>", self.open_class_browser)
257 text.bind("<<open-path-browser>>", self.open_path_browser)
258
Steven M. Gava898a3652001-10-07 11:10:44 +0000259 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000260 vbar['command'] = text.yview
261 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000262 text['yscrollcommand'] = vbar.set
Terry Jan Reedy12352142015-08-01 18:57:27 -0400263 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000264 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
265 text.pack(side=TOP, fill=BOTH, expand=1)
266 text.focus_set()
267
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000268 # usetabs true -> literal tab characters are used by indent and
269 # dedent cmds, possibly mixed with spaces if
270 # indentwidth is not a multiple of tabwidth,
271 # which will cause Tabnanny to nag!
272 # false -> tab characters are converted to spaces by indent
273 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000274 # Although use-spaces=0 can be configured manually in config-main.def,
275 # configuration of tabs v. spaces is not supported in the configuration
276 # dialog. IDLE promotes the preferred Python indentation: use spaces!
277 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
278 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000279
280 # tabwidth is the display width of a literal tab character.
281 # CAUTION: telling Tk to use anything other than its default
282 # tab setting causes it to use an entirely different tabbing algorithm,
283 # treating tab stops as fixed distances from the left margin.
284 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000285 self.tabwidth = 8 # must remain 8 until Tk is fixed.
286
287 # indentwidth is the number of screen characters per indent level.
288 # The recommended Python indentation is four spaces.
289 self.indentwidth = self.tabwidth
290 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000291
292 # If context_use_ps1 is true, parsing searches back for a ps1 line;
293 # else searches for a popular (if, def, ...) Python stmt.
294 self.context_use_ps1 = False
295
296 # When searching backwards for a reliable place to begin parsing,
297 # first start num_context_lines[0] lines back, then
298 # num_context_lines[1] lines back if that didn't work, and so on.
299 # The last value should be huge (larger than the # of lines in a
300 # conceivable file).
301 # Making the initial values larger slows things down more often.
302 self.num_context_lines = 50, 500, 5000000
303
David Scherer7aced172000-08-15 01:13:23 +0000304 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000305
306 self.undo = undo = self.UndoDelegator()
307 per.insertfilter(undo)
308 text.undo_block_start = undo.undo_block_start
309 text.undo_block_stop = undo.undo_block_stop
310 undo.set_saved_change_hook(self.saved_change_hook)
311
312 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000313 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000314 io.set_filename_change_hook(self.filename_change_hook)
315
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000316 # Create the recent files submenu
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400317 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000318 self.menudict['file'].insert_cascade(3, label='Recent Files',
319 underline=0,
320 menu=self.recent_files_menu)
321 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000322
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000323 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000324 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000325 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000326 io.loadfile(filename)
327 else:
328 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000329 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000330 self.saved_change_hook()
331
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000332 self.set_indentation_params(self.ispythonsource(filename))
333
David Scherer7aced172000-08-15 01:13:23 +0000334 self.load_extensions()
335
336 menu = self.menudict.get('windows')
337 if menu:
338 end = menu.index("end")
339 if end is None:
340 end = -1
341 if end >= 0:
342 menu.add_separator()
343 end = end + 1
344 self.wmenu_end = end
345 WindowList.register_callback(self.postwindowsmenu)
346
347 # Some abstractions so IDLE extensions are cross-IDE
348 self.askyesno = tkMessageBox.askyesno
349 self.askinteger = tkSimpleDialog.askinteger
350 self.showerror = tkMessageBox.showerror
351
Martin v. Löwis307021f2005-11-27 16:59:04 +0000352 def _filename_to_unicode(self, filename):
353 """convert filename to unicode in order to display it in Tk"""
354 if isinstance(filename, unicode) or not filename:
355 return filename
356 else:
357 try:
358 return filename.decode(self.filesystemencoding)
359 except UnicodeDecodeError:
360 # XXX
361 try:
362 return filename.decode(self.encoding)
363 except UnicodeDecodeError:
364 # byte-to-byte conversion
365 return filename.decode('iso8859-1')
366
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000367 def new_callback(self, event):
368 dirname, basename = self.io.defaultfilename()
369 self.flist.new(dirname)
370 return "break"
371
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000372 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400373 if (event.state & 4) != 0 and event.keysym == "Home":
374 # state&4==Control. If <Control-Home>, use the Tk binding.
375 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000376 if self.text.index("iomark") and \
377 self.text.compare("iomark", "<=", "insert lineend") and \
378 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400379 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000380 insertpt = int(self.text.index("iomark").split(".")[1])
381 else:
382 line = self.text.get("insert linestart", "insert lineend")
383 for insertpt in xrange(len(line)):
384 if line[insertpt] not in (' ','\t'):
385 break
386 else:
387 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000388 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000389 if insertpt == lineat:
390 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000391 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000392 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400393 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000394 self.text.tag_remove("sel", "1.0", "end")
395 else:
396 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400397 self.text.mark_set("my_anchor", "insert") # there was no previous selection
398 else:
399 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
400 self.text.mark_set("my_anchor", "sel.first") # extend back
401 else:
402 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000403 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400404 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000405 if self.text.compare(first,">",last):
406 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000407 self.text.tag_remove("sel", "1.0", "end")
408 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000409 self.text.mark_set("insert", dest)
410 self.text.see("insert")
411 return "break"
412
David Scherer7aced172000-08-15 01:13:23 +0000413 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000414 self.status_bar = self.MultiStatusBar(self.top)
Ned Deily57847df2014-03-27 20:47:04 -0700415 if sys.platform == "darwin":
Ronald Oussoren19302d92006-06-11 14:33:36 +0000416 # Insert some padding to avoid obscuring some of the statusbar
417 # by the resize widget.
418 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000419 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
420 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
421 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000422 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
423 self.text.event_add("<<set-line-and-column>>",
424 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000425 self.text.after_idle(self.set_line_and_column)
426
427 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000428 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000429 self.status_bar.set_label('column', 'Col: %s' % column)
430 self.status_bar.set_label('line', 'Ln: %s' % line)
431
David Scherer7aced172000-08-15 01:13:23 +0000432 menu_specs = [
433 ("file", "_File"),
434 ("edit", "_Edit"),
435 ("format", "F_ormat"),
436 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000437 ("options", "_Options"),
Ned Deilyb5daa3d2015-01-17 21:03:41 -0800438 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000439 ("help", "_Help"),
440 ]
441
Ronald Oussoren19302d92006-06-11 14:33:36 +0000442
David Scherer7aced172000-08-15 01:13:23 +0000443 def createmenubar(self):
444 mbar = self.menubar
445 self.menudict = menudict = {}
446 for name, label in self.menu_specs:
447 underline, label = prepstr(label)
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400448 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000449 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000450
Ned Deily57847df2014-03-27 20:47:04 -0700451 if macosxSupport.isCarbonTk():
Ronald Oussoren19302d92006-06-11 14:33:36 +0000452 # Insert the application menu
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400453 menudict['application'] = menu = Menu(mbar, name='apple',
454 tearoff=0)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000455 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):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400535 "Handle Help 'About IDLE' event."
536 # Synchronize with macosxSupport.overrideRootMenu.about_dialog.
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000537 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000538
Steven M. Gava3b55a892001-11-21 05:56:26 +0000539 def config_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400540 "Handle Options 'Configure IDLE' event."
541 # Synchronize with macosxSupport.overrideRootMenu.config_dialog.
Steven M. Gava3b55a892001-11-21 05:56:26 +0000542 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400543
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400544 def config_extensions_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400545 "Handle Options 'Configure Extensions' event."
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400546 configDialog.ConfigExtensionsDialog(self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000547
David Scherer7aced172000-08-15 01:13:23 +0000548 def help_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400549 "Handle Help 'IDLE Help' event."
550 # Synchronize with macosxSupport.overrideRootMenu.help_dialog.
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500551 if self.root:
552 parent = self.root
553 else:
554 parent = self.top
Terry Jan Reedy70e763c2015-09-20 19:55:44 -0400555 help.show_idlehelp(parent)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000556
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000557 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000558 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000559 try:
560 os.startfile(self.help_url)
561 except WindowsError as why:
562 tkMessageBox.showerror(title='Document Start Failure',
563 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000564 else:
565 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000566 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000567
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000568 def cut(self,event):
569 self.text.event_generate("<<Cut>>")
570 return "break"
571
572 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000573 if not self.text.tag_ranges("sel"):
574 # There is no selection, so do nothing and maybe interrupt.
575 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000576 self.text.event_generate("<<Copy>>")
577 return "break"
578
579 def paste(self,event):
580 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000581 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000582 return "break"
583
David Scherer7aced172000-08-15 01:13:23 +0000584 def select_all(self, event=None):
585 self.text.tag_add("sel", "1.0", "end-1c")
586 self.text.mark_set("insert", "1.0")
587 self.text.see("insert")
588 return "break"
589
590 def remove_selection(self, event=None):
591 self.text.tag_remove("sel", "1.0", "end")
592 self.text.see("insert")
593
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000594 def move_at_edge_if_selection(self, edge_index):
595 """Cursor move begins at start or end of selection
596
597 When a left/right cursor key is pressed create and return to Tkinter a
598 function which causes a cursor move from the associated edge of the
599 selection.
600
601 """
602 self_text_index = self.text.index
603 self_text_mark_set = self.text.mark_set
604 edges_table = ("sel.first+1c", "sel.last-1c")
605 def move_at_edge(event):
606 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
607 try:
608 self_text_index("sel.first")
609 self_text_mark_set("insert", edges_table[edge_index])
610 except TclError:
611 pass
612 return move_at_edge
613
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000614 def del_word_left(self, event):
615 self.text.event_generate('<Meta-Delete>')
616 return "break"
617
618 def del_word_right(self, event):
619 self.text.event_generate('<Meta-d>')
620 return "break"
621
Steven M. Gavac5976402002-01-04 03:06:08 +0000622 def find_event(self, event):
623 SearchDialog.find(self.text)
624 return "break"
625
626 def find_again_event(self, event):
627 SearchDialog.find_again(self.text)
628 return "break"
629
630 def find_selection_event(self, event):
631 SearchDialog.find_selection(self.text)
632 return "break"
633
634 def find_in_files_event(self, event):
635 GrepDialog.grep(self.text, self.io, self.flist)
636 return "break"
637
638 def replace_event(self, event):
639 ReplaceDialog.replace(self.text)
640 return "break"
641
642 def goto_line_event(self, event):
643 text = self.text
644 lineno = tkSimpleDialog.askinteger("Goto",
645 "Go to line number:",parent=text)
646 if lineno is None:
647 return "break"
648 if lineno <= 0:
649 text.bell()
650 return "break"
651 text.mark_set("insert", "%d.0" % lineno)
652 text.see("insert")
653
David Scherer7aced172000-08-15 01:13:23 +0000654 def open_module(self, event=None):
655 # XXX Shouldn't this be in IOBinding or in FileList?
656 try:
657 name = self.text.get("sel.first", "sel.last")
658 except TclError:
659 name = ""
660 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000661 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000662 name = tkSimpleDialog.askstring("Module",
663 "Enter the name of a Python module\n"
664 "to search on sys.path and open:",
665 parent=self.text, initialvalue=name)
666 if name:
667 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000668 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000669 return
David Scherer7aced172000-08-15 01:13:23 +0000670 # XXX Ought to insert current file's directory in front of path
671 try:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400672 (f, file_path, (suffix, mode, mtype)) = _find_module(name)
Terry Jan Reedy2b149862013-06-29 00:59:34 -0400673 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000674 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
675 return
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400676 if mtype != imp.PY_SOURCE:
David Scherer7aced172000-08-15 01:13:23 +0000677 tkMessageBox.showerror("Unsupported type",
678 "%s is not a source module" % name, parent=self.text)
679 return
680 if f:
681 f.close()
682 if self.flist:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400683 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000684 else:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400685 self.io.loadfile(file_path)
686 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000687
688 def open_class_browser(self, event=None):
689 filename = self.io.filename
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400690 if not (self.__class__.__name__ == 'PyShellEditorWindow'
691 and filename):
692 filename = self.open_module()
693 if filename is None:
694 return
David Scherer7aced172000-08-15 01:13:23 +0000695 head, tail = os.path.split(filename)
696 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000697 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000698 ClassBrowser.ClassBrowser(self.flist, base, [head])
699
700 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000701 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000702 PathBrowser.PathBrowser(self.flist)
703
704 def gotoline(self, lineno):
705 if lineno is not None and lineno > 0:
706 self.text.mark_set("insert", "%d.0" % lineno)
707 self.text.tag_remove("sel", "1.0", "end")
708 self.text.tag_add("sel", "insert", "insert +1l")
709 self.center()
710
711 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000712 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000713 return True
David Scherer7aced172000-08-15 01:13:23 +0000714 base, ext = os.path.splitext(os.path.basename(filename))
715 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000716 return True
David Scherer7aced172000-08-15 01:13:23 +0000717 try:
718 f = open(filename)
719 line = f.readline()
720 f.close()
721 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000722 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000723 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000724
725 def close_hook(self):
726 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000727 self.flist.unregister_maybe_terminate(self)
728 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000729
730 def set_close_hook(self, close_hook):
731 self.close_hook = close_hook
732
733 def filename_change_hook(self):
734 if self.flist:
735 self.flist.filename_changed_edit(self)
736 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000737 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000738 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000739
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000740 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000741 if self.color:
742 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000743 if self.ispythonsource(self.io.filename):
744 self.color = self.ColorDelegator()
745 # can add more colorizers here...
746 if self.color:
747 self.per.removefilter(self.undo)
748 self.per.insertfilter(self.color)
749 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000750
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000751 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000752 if not self.color:
753 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000754 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000755 self.per.removefilter(self.color)
756 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000757
Steven M. Gavab77d3432002-03-02 07:16:21 +0000758 def ResetColorizer(self):
Terry Jan Reedy13755382014-10-09 18:44:26 -0400759 "Update the color theme"
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000760 # Called from self.filename_change_hook and from configDialog.py
761 self._rmcolorizer()
762 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000763 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000764 normal_colors = idleConf.GetHighlight(theme, 'normal')
765 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
766 select_colors = idleConf.GetHighlight(theme, 'hilite')
767 self.text.config(
768 foreground=normal_colors['foreground'],
769 background=normal_colors['background'],
770 insertbackground=cursor_color,
771 selectforeground=select_colors['foreground'],
772 selectbackground=select_colors['background'],
773 )
Terry Jan Reedya6673802015-09-28 04:52:44 -0400774 if TkVersion >= 8.5:
775 self.text.config(
776 inactiveselectbackground=select_colors['background'])
David Scherer7aced172000-08-15 01:13:23 +0000777
Steven M. Gavab1585412002-03-12 00:21:56 +0000778 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000779 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000780 # Called from configDialog.py
Terry Jan Reedy12352142015-08-01 18:57:27 -0400781
782 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000783
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000784 def RemoveKeybindings(self):
785 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000786 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000787 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000788 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000789 self.text.event_delete(event, *keylist)
790 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000791 xkeydefs = idleConf.GetExtensionBindings(extensionName)
792 if xkeydefs:
793 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000794 self.text.event_delete(event, *keylist)
795
796 def ApplyKeybindings(self):
797 "Update the keybindings after they are changed"
798 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000799 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000800 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000801 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000802 xkeydefs = idleConf.GetExtensionBindings(extensionName)
803 if xkeydefs:
804 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000805 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000806 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000807 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000808 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000809 for item in menu[1]:
810 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000811 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000812 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000813 menu = self.menudict[menubarItem]
Ned Deily14ef0c82013-07-20 14:38:24 -0700814 end = menu.index(END)
815 if end is None:
816 # Skip empty menus
817 continue
818 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000819 for index in range(0, end):
820 if menu.type(index) == 'command':
821 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000822 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000823 itemName = menu.entrycget(index, 'label')
824 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000825 if menubarItem in menuEventDict:
826 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000827 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000828 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000829 accel = get_accelerator(keydefs, event)
830 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000831
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000832 def set_notabs_indentwidth(self):
833 "Update the indentwidth if changed and not using tabs in this window"
834 # Called from configDialog.py
835 if not self.usetabs:
836 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
837 type='int')
838
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000839 def reset_help_menu_entries(self):
840 "Update the additional help entries on the Help menu"
841 help_list = idleConf.GetAllExtraHelpSourcesList()
842 helpmenu = self.menudict['help']
843 # first delete the extra help entries, if any
844 helpmenu_length = helpmenu.index(END)
845 if helpmenu_length > self.base_helpmenu_length:
846 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
847 # then rebuild them
848 if help_list:
849 helpmenu.add_separator()
850 for entry in help_list:
851 cmd = self.__extra_help_callback(entry[1])
852 helpmenu.add_command(label=entry[0], command=cmd)
853 # and update the menu dictionary
854 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000855
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000856 def __extra_help_callback(self, helpfile):
857 "Create a callback with the helpfile value frozen at definition time"
858 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000859 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000860 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000861 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000862 try:
863 os.startfile(helpfile)
864 except WindowsError as why:
865 tkMessageBox.showerror(title='Document Start Failure',
866 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000867 else:
868 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000869 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000870
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000871 def update_recent_files_list(self, new_file=None):
872 "Load and update the recent files list and menus"
873 rf_list = []
874 if os.path.exists(self.recent_files_path):
Terry Jan Reedyf9489432013-08-04 15:39:56 -0400875 with open(self.recent_files_path, 'r') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000876 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000877 if new_file:
878 new_file = os.path.abspath(new_file) + '\n'
879 if new_file in rf_list:
880 rf_list.remove(new_file) # move to top
881 rf_list.insert(0, new_file)
882 # clean and save the recent files list
883 bad_paths = []
884 for path in rf_list:
885 if '\0' in path or not os.path.exists(path[0:-1]):
886 bad_paths.append(path)
887 rf_list = [path for path in rf_list if path not in bad_paths]
888 ulchars = "1234567890ABCDEFGHIJK"
889 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000890 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800891 with open(self.recent_files_path, 'w') as rf_file:
892 rf_file.writelines(rf_list)
893 except IOError as err:
894 if not getattr(self.root, "recentfilelist_error_displayed", False):
895 self.root.recentfilelist_error_displayed = True
896 tkMessageBox.showerror(title='IDLE Error',
897 message='Unable to update Recent Files list:\n%s'
898 % str(err),
899 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000900 # for each edit window instance, construct the recent files menu
901 for instance in self.top.instance_dict.keys():
902 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700903 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000904 for i, file_name in enumerate(rf_list):
905 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000906 # make unicode string to display non-ASCII chars correctly
907 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000908 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000909 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000910 command=callback,
911 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000912
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000913 def __recent_file_callback(self, file_name):
914 def open_recent_file(fn_closure=file_name):
915 self.io.open(editFile=fn_closure)
916 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000917
David Scherer7aced172000-08-15 01:13:23 +0000918 def saved_change_hook(self):
919 short = self.short_title()
920 long = self.long_title()
921 if short and long:
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400922 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000923 elif short:
924 title = short
925 elif long:
926 title = long
927 else:
928 title = "Untitled"
929 icon = short or long or title
930 if not self.get_saved():
931 title = "*%s*" % title
932 icon = "*%s" % icon
933 self.top.wm_title(title)
934 self.top.wm_iconname(icon)
935
936 def get_saved(self):
937 return self.undo.get_saved()
938
939 def set_saved(self, flag):
940 self.undo.set_saved(flag)
941
942 def reset_undo(self):
943 self.undo.reset_undo()
944
945 def short_title(self):
946 filename = self.io.filename
947 if filename:
948 filename = os.path.basename(filename)
Terry Jan Reedy59243652014-01-23 00:36:37 -0500949 else:
950 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000951 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400952 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000953
954 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000955 # return unicode string to display non-ASCII chars correctly
956 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000957
958 def center_insert_event(self, event):
959 self.center()
960
961 def center(self, mark="insert"):
962 text = self.text
963 top, bot = self.getwindowlines()
964 lineno = self.getlineno(mark)
965 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000966 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000967 text.yview(float(newtop))
968
969 def getwindowlines(self):
970 text = self.text
971 top = self.getlineno("@0,0")
972 bot = self.getlineno("@0,65535")
973 if top == bot and text.winfo_height() == 1:
974 # Geometry manager hasn't run yet
975 height = int(text['height'])
976 bot = top + height - 1
977 return top, bot
978
979 def getlineno(self, mark="insert"):
980 text = self.text
981 return int(float(text.index(mark)))
982
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000983 def get_geometry(self):
984 "Return (width, height, x, y)"
985 geom = self.top.wm_geometry()
986 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
987 tuple = (map(int, m.groups()))
988 return tuple
989
David Scherer7aced172000-08-15 01:13:23 +0000990 def close_event(self, event):
991 self.close()
992
993 def maybesave(self):
994 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000995 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000996 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000997 self.top.deiconify()
998 self.top.lower()
999 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001000 return self.io.maybesave()
1001
1002 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001003 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +00001004 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001005 self._close()
1006 return reply
1007
1008 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001009 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001010 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001011 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001012 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001013 self.io.close()
1014 self.io = None
1015 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001016 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001017 self.color.close(False)
1018 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001019 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001020 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001021 self.per.close()
1022 self.per = None
1023 self.top.destroy()
1024 if self.close_hook:
1025 # unless override: unregister from flist, terminate if last window
1026 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001027
1028 def load_extensions(self):
1029 self.extensions = {}
1030 self.load_standard_extensions()
1031
1032 def unload_extensions(self):
1033 for ins in self.extensions.values():
1034 if hasattr(ins, "close"):
1035 ins.close()
1036 self.extensions = {}
1037
1038 def load_standard_extensions(self):
1039 for name in self.get_standard_extension_names():
1040 try:
1041 self.load_extension(name)
1042 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001043 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001044 import traceback
1045 traceback.print_exc()
1046
1047 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001048 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001049
1050 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001051 try:
1052 mod = __import__(name, globals(), locals(), [])
1053 except ImportError:
1054 print "\nFailed to import extension: ", name
1055 return
David Scherer7aced172000-08-15 01:13:23 +00001056 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001057 keydefs = idleConf.GetExtensionBindings(name)
1058 if hasattr(cls, "menudefs"):
1059 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001060 ins = cls(self)
1061 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001062 if keydefs:
1063 self.apply_bindings(keydefs)
1064 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001065 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001066 while methodname[:1] == '<':
1067 methodname = methodname[1:]
1068 while methodname[-1:] == '>':
1069 methodname = methodname[:-1]
1070 methodname = methodname + "_event"
1071 if hasattr(ins, methodname):
1072 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001073
1074 def apply_bindings(self, keydefs=None):
1075 if keydefs is None:
1076 keydefs = self.Bindings.default_keydefs
1077 text = self.text
1078 text.keydefs = keydefs
1079 for event, keylist in keydefs.items():
1080 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001081 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001082
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001083 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001084 """Add appropriate entries to the menus and submenus
1085
1086 Menus that are absent or None in self.menudict are ignored.
1087 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001088 if menudefs is None:
1089 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001090 if keydefs is None:
1091 keydefs = self.Bindings.default_keydefs
1092 menudict = self.menudict
1093 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001094 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001095 menu = menudict.get(mname)
1096 if not menu:
1097 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001098 for entry in entrylist:
1099 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001100 menu.add_separator()
1101 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001102 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001103 checkbutton = (label[:1] == '!')
1104 if checkbutton:
1105 label = label[1:]
1106 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001107 accelerator = get_accelerator(keydefs, eventname)
1108 def command(text=text, eventname=eventname):
1109 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001110 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001111 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001112 menu.add_checkbutton(label=label, underline=underline,
1113 command=command, accelerator=accelerator,
1114 variable=var)
1115 else:
1116 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001117 command=command,
1118 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001119
1120 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001121 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001122 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001123 value = var.get()
1124 return value
1125 else:
1126 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001127
1128 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001129 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001130 if var:
1131 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001132 else:
1133 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001134
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001135 def get_var_obj(self, name, vartype=None):
1136 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001137 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001138 # create a Tkinter variable object with self.text as master:
1139 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001140 return var
1141
1142 # Tk implementations of "virtual text methods" -- each platform
1143 # reusing IDLE's support code needs to define these for its GUI's
1144 # flavor of widget.
1145
1146 # Is character at text_index in a Python string? Return 0 for
1147 # "guaranteed no", true for anything else. This info is expensive
1148 # to compute ab initio, but is probably already known by the
1149 # platform's colorizer.
1150
1151 def is_char_in_string(self, text_index):
1152 if self.color:
1153 # Return true iff colorizer hasn't (re)gotten this far
1154 # yet, or the character is tagged as being in a string
1155 return self.text.tag_prevrange("TODO", text_index) or \
1156 "STRING" in self.text.tag_names(text_index)
1157 else:
1158 # The colorizer is missing: assume the worst
1159 return 1
1160
1161 # If a selection is defined in the text widget, return (start,
1162 # end) as Tkinter text indices, otherwise return (None, None)
1163 def get_selection_indices(self):
1164 try:
1165 first = self.text.index("sel.first")
1166 last = self.text.index("sel.last")
1167 return first, last
1168 except TclError:
1169 return None, None
1170
1171 # Return the text widget's current view of what a tab stop means
1172 # (equivalent width in spaces).
1173
1174 def get_tabwidth(self):
1175 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1176 return int(current)
1177
1178 # Set the text widget's current view of what a tab stop means.
1179
1180 def set_tabwidth(self, newtabwidth):
1181 text = self.text
1182 if self.get_tabwidth() != newtabwidth:
1183 pixels = text.tk.call("font", "measure", text["font"],
1184 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001185 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001186 text.configure(tabs=pixels)
1187
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001188 # If ispythonsource and guess are true, guess a good value for
1189 # indentwidth based on file content (if possible), and if
1190 # indentwidth != tabwidth set usetabs false.
1191 # In any case, adjust the Text widget's view of what a tab
1192 # character means.
1193
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001194 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001195 if guess and ispythonsource:
1196 i = self.guess_indent()
1197 if 2 <= i <= 8:
1198 self.indentwidth = i
1199 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001200 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001201 self.set_tabwidth(self.tabwidth)
1202
1203 def smart_backspace_event(self, event):
1204 text = self.text
1205 first, last = self.get_selection_indices()
1206 if first and last:
1207 text.delete(first, last)
1208 text.mark_set("insert", first)
1209 return "break"
1210 # Delete whitespace left, until hitting a real char or closest
1211 # preceding virtual tab stop.
1212 chars = text.get("insert linestart", "insert")
1213 if chars == '':
1214 if text.compare("insert", ">", "1.0"):
1215 # easy: delete preceding newline
1216 text.delete("insert-1c")
1217 else:
1218 text.bell() # at start of buffer
1219 return "break"
1220 if chars[-1] not in " \t":
1221 # easy: delete preceding real char
1222 text.delete("insert-1c")
1223 return "break"
1224 # Ick. It may require *inserting* spaces if we back up over a
1225 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001226 tabwidth = self.tabwidth
1227 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001228 assert have > 0
1229 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001230 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001231 if self.context_use_ps1:
1232 last_line_of_prompt = sys.ps1.split('\n')[-1]
1233 else:
1234 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001235 ncharsdeleted = 0
1236 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001237 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001238 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001239 chars = chars[:-1]
1240 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001241 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001242 if have <= want or chars[-1] not in " \t":
1243 break
1244 text.undo_block_start()
1245 text.delete("insert-%dc" % ncharsdeleted, "insert")
1246 if have < want:
1247 text.insert("insert", ' ' * (want - have))
1248 text.undo_block_stop()
1249 return "break"
1250
1251 def smart_indent_event(self, event):
1252 # if intraline selection:
1253 # delete it
1254 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001255 # do indent-region
1256 # else:
1257 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001258 text = self.text
1259 first, last = self.get_selection_indices()
1260 text.undo_block_start()
1261 try:
1262 if first and last:
1263 if index2line(first) != index2line(last):
1264 return self.indent_region_event(event)
1265 text.delete(first, last)
1266 text.mark_set("insert", first)
1267 prefix = text.get("insert linestart", "insert")
1268 raw, effective = classifyws(prefix, self.tabwidth)
1269 if raw == len(prefix):
1270 # only whitespace to the left
1271 self.reindent_to(effective + self.indentwidth)
1272 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001273 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001274 if self.usetabs:
1275 pad = '\t'
1276 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001277 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001278 n = self.indentwidth
1279 pad = ' ' * (n - effective % n)
1280 text.insert("insert", pad)
1281 text.see("insert")
1282 return "break"
1283 finally:
1284 text.undo_block_stop()
1285
1286 def newline_and_indent_event(self, event):
1287 text = self.text
1288 first, last = self.get_selection_indices()
1289 text.undo_block_start()
1290 try:
1291 if first and last:
1292 text.delete(first, last)
1293 text.mark_set("insert", first)
1294 line = text.get("insert linestart", "insert")
1295 i, n = 0, len(line)
1296 while i < n and line[i] in " \t":
1297 i = i+1
1298 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001299 # the cursor is in or at leading indentation in a continuation
1300 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001301 text.insert("insert linestart", '\n')
1302 return "break"
1303 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001304 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001305 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001306 last_line_of_prompt = sys.ps1.split('\n')[-1]
1307 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001308 line = line[:-1]
1309 i = i+1
1310 if i:
1311 text.delete("insert - %d chars" % i, "insert")
1312 # strip whitespace after insert point
1313 while text.get("insert") in " \t":
1314 text.delete("insert")
1315 # start new line
1316 text.insert("insert", '\n')
1317
1318 # adjust indentation for continuations and block
1319 # open/close first need to find the last stmt
1320 lno = index2line(text.index('insert'))
1321 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001322 if not self.context_use_ps1:
1323 for context in self.num_context_lines:
1324 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001325 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001326 rawtext = text.get(startatindex, "insert")
1327 y.set_str(rawtext)
1328 bod = y.find_good_parse_start(
1329 self.context_use_ps1,
1330 self._build_char_in_string_func(startatindex))
1331 if bod is not None or startat == 1:
1332 break
1333 y.set_lo(bod or 0)
1334 else:
1335 r = text.tag_prevrange("console", "insert")
1336 if r:
1337 startatindex = r[1]
1338 else:
1339 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001340 rawtext = text.get(startatindex, "insert")
1341 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001342 y.set_lo(0)
1343
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001344 c = y.get_continuation_type()
1345 if c != PyParse.C_NONE:
1346 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001347 if c == PyParse.C_STRING_FIRST_LINE:
1348 # after the first line of a string; do not indent at all
1349 pass
1350 elif c == PyParse.C_STRING_NEXT_LINES:
1351 # inside a string which started before this line;
1352 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001353 text.insert("insert", indent)
1354 elif c == PyParse.C_BRACKET:
1355 # line up with the first (if any) element of the
1356 # last open bracket structure; else indent one
1357 # level beyond the indent of the line with the
1358 # last open bracket
1359 self.reindent_to(y.compute_bracket_indent())
1360 elif c == PyParse.C_BACKSLASH:
1361 # if more than one line in this stmt already, just
1362 # mimic the current indent; else if initial line
1363 # has a start on an assignment stmt, indent to
1364 # beyond leftmost =; else to beyond first chunk of
1365 # non-whitespace on initial line
1366 if y.get_num_lines_in_stmt() > 1:
1367 text.insert("insert", indent)
1368 else:
1369 self.reindent_to(y.compute_backslash_indent())
1370 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001371 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001372 return "break"
1373
1374 # This line starts a brand new stmt; indent relative to
1375 # indentation of initial line of closest preceding
1376 # interesting stmt.
1377 indent = y.get_base_indent_string()
1378 text.insert("insert", indent)
1379 if y.is_block_opener():
1380 self.smart_indent_event(event)
1381 elif indent and y.is_block_closer():
1382 self.smart_backspace_event(event)
1383 return "break"
1384 finally:
1385 text.see("insert")
1386 text.undo_block_stop()
1387
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001388 # Our editwin provides a is_char_in_string function that works
1389 # with a Tk text index, but PyParse only knows about offsets into
1390 # a string. This builds a function for PyParse that accepts an
1391 # offset.
1392
1393 def _build_char_in_string_func(self, startindex):
1394 def inner(offset, _startindex=startindex,
1395 _icis=self.is_char_in_string):
1396 return _icis(_startindex + "+%dc" % offset)
1397 return inner
1398
1399 def indent_region_event(self, event):
1400 head, tail, chars, lines = self.get_region()
1401 for pos in range(len(lines)):
1402 line = lines[pos]
1403 if line:
1404 raw, effective = classifyws(line, self.tabwidth)
1405 effective = effective + self.indentwidth
1406 lines[pos] = self._make_blanks(effective) + line[raw:]
1407 self.set_region(head, tail, chars, lines)
1408 return "break"
1409
1410 def dedent_region_event(self, event):
1411 head, tail, chars, lines = self.get_region()
1412 for pos in range(len(lines)):
1413 line = lines[pos]
1414 if line:
1415 raw, effective = classifyws(line, self.tabwidth)
1416 effective = max(effective - self.indentwidth, 0)
1417 lines[pos] = self._make_blanks(effective) + line[raw:]
1418 self.set_region(head, tail, chars, lines)
1419 return "break"
1420
1421 def comment_region_event(self, event):
1422 head, tail, chars, lines = self.get_region()
1423 for pos in range(len(lines) - 1):
1424 line = lines[pos]
1425 lines[pos] = '##' + line
1426 self.set_region(head, tail, chars, lines)
1427
1428 def uncomment_region_event(self, event):
1429 head, tail, chars, lines = self.get_region()
1430 for pos in range(len(lines)):
1431 line = lines[pos]
1432 if not line:
1433 continue
1434 if line[:2] == '##':
1435 line = line[2:]
1436 elif line[:1] == '#':
1437 line = line[1:]
1438 lines[pos] = line
1439 self.set_region(head, tail, chars, lines)
1440
1441 def tabify_region_event(self, event):
1442 head, tail, chars, lines = self.get_region()
1443 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001444 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001445 for pos in range(len(lines)):
1446 line = lines[pos]
1447 if line:
1448 raw, effective = classifyws(line, tabwidth)
1449 ntabs, nspaces = divmod(effective, tabwidth)
1450 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1451 self.set_region(head, tail, chars, lines)
1452
1453 def untabify_region_event(self, event):
1454 head, tail, chars, lines = self.get_region()
1455 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001456 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001457 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001458 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001459 self.set_region(head, tail, chars, lines)
1460
1461 def toggle_tabs_event(self, event):
1462 if self.askyesno(
1463 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001464 "Turn tabs " + ("on", "off")[self.usetabs] +
1465 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001466 ("will be", "remains at")[self.usetabs] + " 8." +
1467 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001468 parent=self.text):
1469 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001470 # Try to prevent inconsistent indentation.
1471 # User must change indent width manually after using tabs.
1472 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001473 return "break"
1474
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001475 # XXX this isn't bound to anything -- see tabwidth comments
1476## def change_tabwidth_event(self, event):
1477## new = self._asktabwidth()
1478## if new != self.tabwidth:
1479## self.tabwidth = new
1480## self.set_indentation_params(0, guess=0)
1481## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001482
1483 def change_indentwidth_event(self, event):
1484 new = self.askinteger(
1485 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001486 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001487 parent=self.text,
1488 initialvalue=self.indentwidth,
1489 minvalue=2,
1490 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001491 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001492 self.indentwidth = new
1493 return "break"
1494
1495 def get_region(self):
1496 text = self.text
1497 first, last = self.get_selection_indices()
1498 if first and last:
1499 head = text.index(first + " linestart")
1500 tail = text.index(last + "-1c lineend +1c")
1501 else:
1502 head = text.index("insert linestart")
1503 tail = text.index("insert lineend +1c")
1504 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001505 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001506 return head, tail, chars, lines
1507
1508 def set_region(self, head, tail, chars, lines):
1509 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001510 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001511 if newchars == chars:
1512 text.bell()
1513 return
1514 text.tag_remove("sel", "1.0", "end")
1515 text.mark_set("insert", head)
1516 text.undo_block_start()
1517 text.delete(head, tail)
1518 text.insert(head, newchars)
1519 text.undo_block_stop()
1520 text.tag_add("sel", head, "insert")
1521
1522 # Make string that displays as n leading blanks.
1523
1524 def _make_blanks(self, n):
1525 if self.usetabs:
1526 ntabs, nspaces = divmod(n, self.tabwidth)
1527 return '\t' * ntabs + ' ' * nspaces
1528 else:
1529 return ' ' * n
1530
1531 # Delete from beginning of line to insert point, then reinsert
1532 # column logical (meaning use tabs if appropriate) spaces.
1533
1534 def reindent_to(self, column):
1535 text = self.text
1536 text.undo_block_start()
1537 if text.compare("insert linestart", "!=", "insert"):
1538 text.delete("insert linestart", "insert")
1539 if column:
1540 text.insert("insert", self._make_blanks(column))
1541 text.undo_block_stop()
1542
1543 def _asktabwidth(self):
1544 return self.askinteger(
1545 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001546 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001547 parent=self.text,
1548 initialvalue=self.indentwidth,
1549 minvalue=2,
Roger Serwy75b249c2013-04-06 20:26:53 -05001550 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001551
1552 # Guess indentwidth from text content.
1553 # Return guessed indentwidth. This should not be believed unless
1554 # it's in a reasonable range (e.g., it will be 0 if no indented
1555 # blocks are found).
1556
1557 def guess_indent(self):
1558 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1559 if opener and indented:
1560 raw, indentsmall = classifyws(opener, self.tabwidth)
1561 raw, indentlarge = classifyws(indented, self.tabwidth)
1562 else:
1563 indentsmall = indentlarge = 0
1564 return indentlarge - indentsmall
1565
1566# "line.col" -> line, as an int
1567def index2line(index):
1568 return int(float(index))
1569
1570# Look at the leading whitespace in s.
1571# Return pair (# of leading ws characters,
1572# effective # of leading blanks after expanding
1573# tabs to width tabwidth)
1574
1575def classifyws(s, tabwidth):
1576 raw = effective = 0
1577 for ch in s:
1578 if ch == ' ':
1579 raw = raw + 1
1580 effective = effective + 1
1581 elif ch == '\t':
1582 raw = raw + 1
1583 effective = (effective // tabwidth + 1) * tabwidth
1584 else:
1585 break
1586 return raw, effective
1587
1588import tokenize
1589_tokenize = tokenize
1590del tokenize
1591
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001592class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001593
1594 # .run() chews over the Text widget, looking for a block opener
1595 # and the stmt following it. Returns a pair,
1596 # (line containing block opener, line containing stmt)
1597 # Either or both may be None.
1598
1599 def __init__(self, text, tabwidth):
1600 self.text = text
1601 self.tabwidth = tabwidth
1602 self.i = self.finished = 0
1603 self.blkopenline = self.indentedline = None
1604
1605 def readline(self):
1606 if self.finished:
1607 return ""
1608 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001609 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001610 if self.text.compare(mark, ">=", "end"):
1611 return ""
1612 return self.text.get(mark, mark + " lineend+1c")
1613
1614 def tokeneater(self, type, token, start, end, line,
1615 INDENT=_tokenize.INDENT,
1616 NAME=_tokenize.NAME,
1617 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1618 if self.finished:
1619 pass
1620 elif type == NAME and token in OPENERS:
1621 self.blkopenline = line
1622 elif type == INDENT and self.blkopenline:
1623 self.indentedline = line
1624 self.finished = 1
1625
1626 def run(self):
1627 save_tabsize = _tokenize.tabsize
1628 _tokenize.tabsize = self.tabwidth
1629 try:
1630 try:
1631 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001632 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001633 # since we cut off the tokenizer early, we can trigger
1634 # spurious errors
1635 pass
1636 finally:
1637 _tokenize.tabsize = save_tabsize
1638 return self.blkopenline, self.indentedline
1639
1640### end autoindent code ###
1641
David Scherer7aced172000-08-15 01:13:23 +00001642def prepstr(s):
1643 # Helper to extract the underscore from a string, e.g.
1644 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001645 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001646 if i >= 0:
1647 s = s[:i] + s[i+1:]
1648 return i, s
1649
1650
1651keynames = {
1652 'bracketleft': '[',
1653 'bracketright': ']',
1654 'slash': '/',
1655}
1656
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001657def get_accelerator(keydefs, eventname):
1658 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001659 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1660 # if not keylist:
Ned Deily57847df2014-03-27 20:47:04 -07001661 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily60651532011-01-31 00:52:49 +00001662 "<<open-module>>",
1663 "<<goto-line>>",
1664 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001665 return ""
1666 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001667 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001668 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1669 s = re.sub("Key-", "", s)
1670 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1671 s = re.sub("Control-", "Ctrl-", s)
1672 s = re.sub("-", "+", s)
1673 s = re.sub("><", " ", s)
1674 s = re.sub("<", "", s)
1675 s = re.sub(">", "", s)
1676 return s
1677
1678
1679def fixwordbreaks(root):
1680 # Make sure that Tk's double-click and next/previous word
1681 # operations use our definition of a word (i.e. an identifier)
1682 tk = root.tk
1683 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1684 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1685 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1686
1687
Terry Jan Reedycf834762014-10-17 01:31:29 -04001688def _editor_window(parent): # htest #
1689 # error if close master window first - timer event, after script
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001690 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001691 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001692 if sys.argv[1:]:
1693 filename = sys.argv[1]
1694 else:
1695 filename = None
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001696 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001697 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001698 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycf834762014-10-17 01:31:29 -04001699 # Does not stop error, neither does following
1700 # edit.text.bind("<<close-window>>", edit.close_event)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001701
David Scherer7aced172000-08-15 01:13:23 +00001702
1703if __name__ == '__main__':
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001704 from idlelib.idle_test.htest import run
Terry Jan Reedy70e763c2015-09-20 19:55:44 -04001705 run(_editor_window)