blob: 1f4bd9dd62b320c0863974ec5a531a7a666cf78d [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'],
Terry Jan Reedy62520b22015-09-27 22:46:12 -0400773 inactiveselectbackground=select_colors['background'],
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000774 )
David Scherer7aced172000-08-15 01:13:23 +0000775
Steven M. Gavab1585412002-03-12 00:21:56 +0000776 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000777 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000778 # Called from configDialog.py
Terry Jan Reedy12352142015-08-01 18:57:27 -0400779
780 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000781
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000782 def RemoveKeybindings(self):
783 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000784 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000785 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000786 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000787 self.text.event_delete(event, *keylist)
788 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000789 xkeydefs = idleConf.GetExtensionBindings(extensionName)
790 if xkeydefs:
791 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000792 self.text.event_delete(event, *keylist)
793
794 def ApplyKeybindings(self):
795 "Update the keybindings after they are changed"
796 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000797 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000798 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000799 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000800 xkeydefs = idleConf.GetExtensionBindings(extensionName)
801 if xkeydefs:
802 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000803 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000804 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000805 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000806 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000807 for item in menu[1]:
808 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000809 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000810 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000811 menu = self.menudict[menubarItem]
Ned Deily14ef0c82013-07-20 14:38:24 -0700812 end = menu.index(END)
813 if end is None:
814 # Skip empty menus
815 continue
816 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000817 for index in range(0, end):
818 if menu.type(index) == 'command':
819 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000820 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000821 itemName = menu.entrycget(index, 'label')
822 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000823 if menubarItem in menuEventDict:
824 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000825 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000826 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000827 accel = get_accelerator(keydefs, event)
828 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000829
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000830 def set_notabs_indentwidth(self):
831 "Update the indentwidth if changed and not using tabs in this window"
832 # Called from configDialog.py
833 if not self.usetabs:
834 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
835 type='int')
836
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000837 def reset_help_menu_entries(self):
838 "Update the additional help entries on the Help menu"
839 help_list = idleConf.GetAllExtraHelpSourcesList()
840 helpmenu = self.menudict['help']
841 # first delete the extra help entries, if any
842 helpmenu_length = helpmenu.index(END)
843 if helpmenu_length > self.base_helpmenu_length:
844 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
845 # then rebuild them
846 if help_list:
847 helpmenu.add_separator()
848 for entry in help_list:
849 cmd = self.__extra_help_callback(entry[1])
850 helpmenu.add_command(label=entry[0], command=cmd)
851 # and update the menu dictionary
852 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000853
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000854 def __extra_help_callback(self, helpfile):
855 "Create a callback with the helpfile value frozen at definition time"
856 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000857 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000858 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000859 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000860 try:
861 os.startfile(helpfile)
862 except WindowsError as why:
863 tkMessageBox.showerror(title='Document Start Failure',
864 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000865 else:
866 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000867 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000868
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000869 def update_recent_files_list(self, new_file=None):
870 "Load and update the recent files list and menus"
871 rf_list = []
872 if os.path.exists(self.recent_files_path):
Terry Jan Reedyf9489432013-08-04 15:39:56 -0400873 with open(self.recent_files_path, 'r') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000874 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000875 if new_file:
876 new_file = os.path.abspath(new_file) + '\n'
877 if new_file in rf_list:
878 rf_list.remove(new_file) # move to top
879 rf_list.insert(0, new_file)
880 # clean and save the recent files list
881 bad_paths = []
882 for path in rf_list:
883 if '\0' in path or not os.path.exists(path[0:-1]):
884 bad_paths.append(path)
885 rf_list = [path for path in rf_list if path not in bad_paths]
886 ulchars = "1234567890ABCDEFGHIJK"
887 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000888 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800889 with open(self.recent_files_path, 'w') as rf_file:
890 rf_file.writelines(rf_list)
891 except IOError as err:
892 if not getattr(self.root, "recentfilelist_error_displayed", False):
893 self.root.recentfilelist_error_displayed = True
894 tkMessageBox.showerror(title='IDLE Error',
895 message='Unable to update Recent Files list:\n%s'
896 % str(err),
897 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000898 # for each edit window instance, construct the recent files menu
899 for instance in self.top.instance_dict.keys():
900 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700901 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000902 for i, file_name in enumerate(rf_list):
903 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000904 # make unicode string to display non-ASCII chars correctly
905 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000906 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000907 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000908 command=callback,
909 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000910
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000911 def __recent_file_callback(self, file_name):
912 def open_recent_file(fn_closure=file_name):
913 self.io.open(editFile=fn_closure)
914 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000915
David Scherer7aced172000-08-15 01:13:23 +0000916 def saved_change_hook(self):
917 short = self.short_title()
918 long = self.long_title()
919 if short and long:
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400920 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000921 elif short:
922 title = short
923 elif long:
924 title = long
925 else:
926 title = "Untitled"
927 icon = short or long or title
928 if not self.get_saved():
929 title = "*%s*" % title
930 icon = "*%s" % icon
931 self.top.wm_title(title)
932 self.top.wm_iconname(icon)
933
934 def get_saved(self):
935 return self.undo.get_saved()
936
937 def set_saved(self, flag):
938 self.undo.set_saved(flag)
939
940 def reset_undo(self):
941 self.undo.reset_undo()
942
943 def short_title(self):
944 filename = self.io.filename
945 if filename:
946 filename = os.path.basename(filename)
Terry Jan Reedy59243652014-01-23 00:36:37 -0500947 else:
948 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000949 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400950 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000951
952 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000953 # return unicode string to display non-ASCII chars correctly
954 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000955
956 def center_insert_event(self, event):
957 self.center()
958
959 def center(self, mark="insert"):
960 text = self.text
961 top, bot = self.getwindowlines()
962 lineno = self.getlineno(mark)
963 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000964 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000965 text.yview(float(newtop))
966
967 def getwindowlines(self):
968 text = self.text
969 top = self.getlineno("@0,0")
970 bot = self.getlineno("@0,65535")
971 if top == bot and text.winfo_height() == 1:
972 # Geometry manager hasn't run yet
973 height = int(text['height'])
974 bot = top + height - 1
975 return top, bot
976
977 def getlineno(self, mark="insert"):
978 text = self.text
979 return int(float(text.index(mark)))
980
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000981 def get_geometry(self):
982 "Return (width, height, x, y)"
983 geom = self.top.wm_geometry()
984 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
985 tuple = (map(int, m.groups()))
986 return tuple
987
David Scherer7aced172000-08-15 01:13:23 +0000988 def close_event(self, event):
989 self.close()
990
991 def maybesave(self):
992 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000993 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000994 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000995 self.top.deiconify()
996 self.top.lower()
997 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000998 return self.io.maybesave()
999
1000 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001001 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +00001002 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001003 self._close()
1004 return reply
1005
1006 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001007 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001008 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001009 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001010 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001011 self.io.close()
1012 self.io = None
1013 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001014 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001015 self.color.close(False)
1016 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001017 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001018 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001019 self.per.close()
1020 self.per = None
1021 self.top.destroy()
1022 if self.close_hook:
1023 # unless override: unregister from flist, terminate if last window
1024 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001025
1026 def load_extensions(self):
1027 self.extensions = {}
1028 self.load_standard_extensions()
1029
1030 def unload_extensions(self):
1031 for ins in self.extensions.values():
1032 if hasattr(ins, "close"):
1033 ins.close()
1034 self.extensions = {}
1035
1036 def load_standard_extensions(self):
1037 for name in self.get_standard_extension_names():
1038 try:
1039 self.load_extension(name)
1040 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001041 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001042 import traceback
1043 traceback.print_exc()
1044
1045 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001046 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001047
1048 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001049 try:
1050 mod = __import__(name, globals(), locals(), [])
1051 except ImportError:
1052 print "\nFailed to import extension: ", name
1053 return
David Scherer7aced172000-08-15 01:13:23 +00001054 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001055 keydefs = idleConf.GetExtensionBindings(name)
1056 if hasattr(cls, "menudefs"):
1057 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001058 ins = cls(self)
1059 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001060 if keydefs:
1061 self.apply_bindings(keydefs)
1062 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001063 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001064 while methodname[:1] == '<':
1065 methodname = methodname[1:]
1066 while methodname[-1:] == '>':
1067 methodname = methodname[:-1]
1068 methodname = methodname + "_event"
1069 if hasattr(ins, methodname):
1070 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001071
1072 def apply_bindings(self, keydefs=None):
1073 if keydefs is None:
1074 keydefs = self.Bindings.default_keydefs
1075 text = self.text
1076 text.keydefs = keydefs
1077 for event, keylist in keydefs.items():
1078 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001079 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001080
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001081 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001082 """Add appropriate entries to the menus and submenus
1083
1084 Menus that are absent or None in self.menudict are ignored.
1085 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001086 if menudefs is None:
1087 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001088 if keydefs is None:
1089 keydefs = self.Bindings.default_keydefs
1090 menudict = self.menudict
1091 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001092 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001093 menu = menudict.get(mname)
1094 if not menu:
1095 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001096 for entry in entrylist:
1097 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001098 menu.add_separator()
1099 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001100 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001101 checkbutton = (label[:1] == '!')
1102 if checkbutton:
1103 label = label[1:]
1104 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001105 accelerator = get_accelerator(keydefs, eventname)
1106 def command(text=text, eventname=eventname):
1107 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001108 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001109 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001110 menu.add_checkbutton(label=label, underline=underline,
1111 command=command, accelerator=accelerator,
1112 variable=var)
1113 else:
1114 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001115 command=command,
1116 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001117
1118 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001119 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001120 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001121 value = var.get()
1122 return value
1123 else:
1124 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001125
1126 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001127 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001128 if var:
1129 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001130 else:
1131 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001132
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001133 def get_var_obj(self, name, vartype=None):
1134 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001135 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001136 # create a Tkinter variable object with self.text as master:
1137 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001138 return var
1139
1140 # Tk implementations of "virtual text methods" -- each platform
1141 # reusing IDLE's support code needs to define these for its GUI's
1142 # flavor of widget.
1143
1144 # Is character at text_index in a Python string? Return 0 for
1145 # "guaranteed no", true for anything else. This info is expensive
1146 # to compute ab initio, but is probably already known by the
1147 # platform's colorizer.
1148
1149 def is_char_in_string(self, text_index):
1150 if self.color:
1151 # Return true iff colorizer hasn't (re)gotten this far
1152 # yet, or the character is tagged as being in a string
1153 return self.text.tag_prevrange("TODO", text_index) or \
1154 "STRING" in self.text.tag_names(text_index)
1155 else:
1156 # The colorizer is missing: assume the worst
1157 return 1
1158
1159 # If a selection is defined in the text widget, return (start,
1160 # end) as Tkinter text indices, otherwise return (None, None)
1161 def get_selection_indices(self):
1162 try:
1163 first = self.text.index("sel.first")
1164 last = self.text.index("sel.last")
1165 return first, last
1166 except TclError:
1167 return None, None
1168
1169 # Return the text widget's current view of what a tab stop means
1170 # (equivalent width in spaces).
1171
1172 def get_tabwidth(self):
1173 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1174 return int(current)
1175
1176 # Set the text widget's current view of what a tab stop means.
1177
1178 def set_tabwidth(self, newtabwidth):
1179 text = self.text
1180 if self.get_tabwidth() != newtabwidth:
1181 pixels = text.tk.call("font", "measure", text["font"],
1182 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001183 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001184 text.configure(tabs=pixels)
1185
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001186 # If ispythonsource and guess are true, guess a good value for
1187 # indentwidth based on file content (if possible), and if
1188 # indentwidth != tabwidth set usetabs false.
1189 # In any case, adjust the Text widget's view of what a tab
1190 # character means.
1191
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001192 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001193 if guess and ispythonsource:
1194 i = self.guess_indent()
1195 if 2 <= i <= 8:
1196 self.indentwidth = i
1197 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001198 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001199 self.set_tabwidth(self.tabwidth)
1200
1201 def smart_backspace_event(self, event):
1202 text = self.text
1203 first, last = self.get_selection_indices()
1204 if first and last:
1205 text.delete(first, last)
1206 text.mark_set("insert", first)
1207 return "break"
1208 # Delete whitespace left, until hitting a real char or closest
1209 # preceding virtual tab stop.
1210 chars = text.get("insert linestart", "insert")
1211 if chars == '':
1212 if text.compare("insert", ">", "1.0"):
1213 # easy: delete preceding newline
1214 text.delete("insert-1c")
1215 else:
1216 text.bell() # at start of buffer
1217 return "break"
1218 if chars[-1] not in " \t":
1219 # easy: delete preceding real char
1220 text.delete("insert-1c")
1221 return "break"
1222 # Ick. It may require *inserting* spaces if we back up over a
1223 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001224 tabwidth = self.tabwidth
1225 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001226 assert have > 0
1227 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001228 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001229 if self.context_use_ps1:
1230 last_line_of_prompt = sys.ps1.split('\n')[-1]
1231 else:
1232 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001233 ncharsdeleted = 0
1234 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001235 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001236 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001237 chars = chars[:-1]
1238 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001239 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001240 if have <= want or chars[-1] not in " \t":
1241 break
1242 text.undo_block_start()
1243 text.delete("insert-%dc" % ncharsdeleted, "insert")
1244 if have < want:
1245 text.insert("insert", ' ' * (want - have))
1246 text.undo_block_stop()
1247 return "break"
1248
1249 def smart_indent_event(self, event):
1250 # if intraline selection:
1251 # delete it
1252 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001253 # do indent-region
1254 # else:
1255 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001256 text = self.text
1257 first, last = self.get_selection_indices()
1258 text.undo_block_start()
1259 try:
1260 if first and last:
1261 if index2line(first) != index2line(last):
1262 return self.indent_region_event(event)
1263 text.delete(first, last)
1264 text.mark_set("insert", first)
1265 prefix = text.get("insert linestart", "insert")
1266 raw, effective = classifyws(prefix, self.tabwidth)
1267 if raw == len(prefix):
1268 # only whitespace to the left
1269 self.reindent_to(effective + self.indentwidth)
1270 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001271 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001272 if self.usetabs:
1273 pad = '\t'
1274 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001275 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001276 n = self.indentwidth
1277 pad = ' ' * (n - effective % n)
1278 text.insert("insert", pad)
1279 text.see("insert")
1280 return "break"
1281 finally:
1282 text.undo_block_stop()
1283
1284 def newline_and_indent_event(self, event):
1285 text = self.text
1286 first, last = self.get_selection_indices()
1287 text.undo_block_start()
1288 try:
1289 if first and last:
1290 text.delete(first, last)
1291 text.mark_set("insert", first)
1292 line = text.get("insert linestart", "insert")
1293 i, n = 0, len(line)
1294 while i < n and line[i] in " \t":
1295 i = i+1
1296 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001297 # the cursor is in or at leading indentation in a continuation
1298 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001299 text.insert("insert linestart", '\n')
1300 return "break"
1301 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001302 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001303 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001304 last_line_of_prompt = sys.ps1.split('\n')[-1]
1305 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001306 line = line[:-1]
1307 i = i+1
1308 if i:
1309 text.delete("insert - %d chars" % i, "insert")
1310 # strip whitespace after insert point
1311 while text.get("insert") in " \t":
1312 text.delete("insert")
1313 # start new line
1314 text.insert("insert", '\n')
1315
1316 # adjust indentation for continuations and block
1317 # open/close first need to find the last stmt
1318 lno = index2line(text.index('insert'))
1319 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001320 if not self.context_use_ps1:
1321 for context in self.num_context_lines:
1322 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001323 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001324 rawtext = text.get(startatindex, "insert")
1325 y.set_str(rawtext)
1326 bod = y.find_good_parse_start(
1327 self.context_use_ps1,
1328 self._build_char_in_string_func(startatindex))
1329 if bod is not None or startat == 1:
1330 break
1331 y.set_lo(bod or 0)
1332 else:
1333 r = text.tag_prevrange("console", "insert")
1334 if r:
1335 startatindex = r[1]
1336 else:
1337 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001338 rawtext = text.get(startatindex, "insert")
1339 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001340 y.set_lo(0)
1341
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001342 c = y.get_continuation_type()
1343 if c != PyParse.C_NONE:
1344 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001345 if c == PyParse.C_STRING_FIRST_LINE:
1346 # after the first line of a string; do not indent at all
1347 pass
1348 elif c == PyParse.C_STRING_NEXT_LINES:
1349 # inside a string which started before this line;
1350 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001351 text.insert("insert", indent)
1352 elif c == PyParse.C_BRACKET:
1353 # line up with the first (if any) element of the
1354 # last open bracket structure; else indent one
1355 # level beyond the indent of the line with the
1356 # last open bracket
1357 self.reindent_to(y.compute_bracket_indent())
1358 elif c == PyParse.C_BACKSLASH:
1359 # if more than one line in this stmt already, just
1360 # mimic the current indent; else if initial line
1361 # has a start on an assignment stmt, indent to
1362 # beyond leftmost =; else to beyond first chunk of
1363 # non-whitespace on initial line
1364 if y.get_num_lines_in_stmt() > 1:
1365 text.insert("insert", indent)
1366 else:
1367 self.reindent_to(y.compute_backslash_indent())
1368 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001369 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001370 return "break"
1371
1372 # This line starts a brand new stmt; indent relative to
1373 # indentation of initial line of closest preceding
1374 # interesting stmt.
1375 indent = y.get_base_indent_string()
1376 text.insert("insert", indent)
1377 if y.is_block_opener():
1378 self.smart_indent_event(event)
1379 elif indent and y.is_block_closer():
1380 self.smart_backspace_event(event)
1381 return "break"
1382 finally:
1383 text.see("insert")
1384 text.undo_block_stop()
1385
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001386 # Our editwin provides a is_char_in_string function that works
1387 # with a Tk text index, but PyParse only knows about offsets into
1388 # a string. This builds a function for PyParse that accepts an
1389 # offset.
1390
1391 def _build_char_in_string_func(self, startindex):
1392 def inner(offset, _startindex=startindex,
1393 _icis=self.is_char_in_string):
1394 return _icis(_startindex + "+%dc" % offset)
1395 return inner
1396
1397 def indent_region_event(self, event):
1398 head, tail, chars, lines = self.get_region()
1399 for pos in range(len(lines)):
1400 line = lines[pos]
1401 if line:
1402 raw, effective = classifyws(line, self.tabwidth)
1403 effective = effective + self.indentwidth
1404 lines[pos] = self._make_blanks(effective) + line[raw:]
1405 self.set_region(head, tail, chars, lines)
1406 return "break"
1407
1408 def dedent_region_event(self, event):
1409 head, tail, chars, lines = self.get_region()
1410 for pos in range(len(lines)):
1411 line = lines[pos]
1412 if line:
1413 raw, effective = classifyws(line, self.tabwidth)
1414 effective = max(effective - self.indentwidth, 0)
1415 lines[pos] = self._make_blanks(effective) + line[raw:]
1416 self.set_region(head, tail, chars, lines)
1417 return "break"
1418
1419 def comment_region_event(self, event):
1420 head, tail, chars, lines = self.get_region()
1421 for pos in range(len(lines) - 1):
1422 line = lines[pos]
1423 lines[pos] = '##' + line
1424 self.set_region(head, tail, chars, lines)
1425
1426 def uncomment_region_event(self, event):
1427 head, tail, chars, lines = self.get_region()
1428 for pos in range(len(lines)):
1429 line = lines[pos]
1430 if not line:
1431 continue
1432 if line[:2] == '##':
1433 line = line[2:]
1434 elif line[:1] == '#':
1435 line = line[1:]
1436 lines[pos] = line
1437 self.set_region(head, tail, chars, lines)
1438
1439 def tabify_region_event(self, event):
1440 head, tail, chars, lines = self.get_region()
1441 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001442 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001443 for pos in range(len(lines)):
1444 line = lines[pos]
1445 if line:
1446 raw, effective = classifyws(line, tabwidth)
1447 ntabs, nspaces = divmod(effective, tabwidth)
1448 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1449 self.set_region(head, tail, chars, lines)
1450
1451 def untabify_region_event(self, event):
1452 head, tail, chars, lines = self.get_region()
1453 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001454 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001455 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001456 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001457 self.set_region(head, tail, chars, lines)
1458
1459 def toggle_tabs_event(self, event):
1460 if self.askyesno(
1461 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001462 "Turn tabs " + ("on", "off")[self.usetabs] +
1463 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001464 ("will be", "remains at")[self.usetabs] + " 8." +
1465 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001466 parent=self.text):
1467 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001468 # Try to prevent inconsistent indentation.
1469 # User must change indent width manually after using tabs.
1470 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001471 return "break"
1472
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001473 # XXX this isn't bound to anything -- see tabwidth comments
1474## def change_tabwidth_event(self, event):
1475## new = self._asktabwidth()
1476## if new != self.tabwidth:
1477## self.tabwidth = new
1478## self.set_indentation_params(0, guess=0)
1479## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001480
1481 def change_indentwidth_event(self, event):
1482 new = self.askinteger(
1483 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001484 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001485 parent=self.text,
1486 initialvalue=self.indentwidth,
1487 minvalue=2,
1488 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001489 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001490 self.indentwidth = new
1491 return "break"
1492
1493 def get_region(self):
1494 text = self.text
1495 first, last = self.get_selection_indices()
1496 if first and last:
1497 head = text.index(first + " linestart")
1498 tail = text.index(last + "-1c lineend +1c")
1499 else:
1500 head = text.index("insert linestart")
1501 tail = text.index("insert lineend +1c")
1502 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001503 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001504 return head, tail, chars, lines
1505
1506 def set_region(self, head, tail, chars, lines):
1507 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001508 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001509 if newchars == chars:
1510 text.bell()
1511 return
1512 text.tag_remove("sel", "1.0", "end")
1513 text.mark_set("insert", head)
1514 text.undo_block_start()
1515 text.delete(head, tail)
1516 text.insert(head, newchars)
1517 text.undo_block_stop()
1518 text.tag_add("sel", head, "insert")
1519
1520 # Make string that displays as n leading blanks.
1521
1522 def _make_blanks(self, n):
1523 if self.usetabs:
1524 ntabs, nspaces = divmod(n, self.tabwidth)
1525 return '\t' * ntabs + ' ' * nspaces
1526 else:
1527 return ' ' * n
1528
1529 # Delete from beginning of line to insert point, then reinsert
1530 # column logical (meaning use tabs if appropriate) spaces.
1531
1532 def reindent_to(self, column):
1533 text = self.text
1534 text.undo_block_start()
1535 if text.compare("insert linestart", "!=", "insert"):
1536 text.delete("insert linestart", "insert")
1537 if column:
1538 text.insert("insert", self._make_blanks(column))
1539 text.undo_block_stop()
1540
1541 def _asktabwidth(self):
1542 return self.askinteger(
1543 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001544 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001545 parent=self.text,
1546 initialvalue=self.indentwidth,
1547 minvalue=2,
Roger Serwy75b249c2013-04-06 20:26:53 -05001548 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001549
1550 # Guess indentwidth from text content.
1551 # Return guessed indentwidth. This should not be believed unless
1552 # it's in a reasonable range (e.g., it will be 0 if no indented
1553 # blocks are found).
1554
1555 def guess_indent(self):
1556 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1557 if opener and indented:
1558 raw, indentsmall = classifyws(opener, self.tabwidth)
1559 raw, indentlarge = classifyws(indented, self.tabwidth)
1560 else:
1561 indentsmall = indentlarge = 0
1562 return indentlarge - indentsmall
1563
1564# "line.col" -> line, as an int
1565def index2line(index):
1566 return int(float(index))
1567
1568# Look at the leading whitespace in s.
1569# Return pair (# of leading ws characters,
1570# effective # of leading blanks after expanding
1571# tabs to width tabwidth)
1572
1573def classifyws(s, tabwidth):
1574 raw = effective = 0
1575 for ch in s:
1576 if ch == ' ':
1577 raw = raw + 1
1578 effective = effective + 1
1579 elif ch == '\t':
1580 raw = raw + 1
1581 effective = (effective // tabwidth + 1) * tabwidth
1582 else:
1583 break
1584 return raw, effective
1585
1586import tokenize
1587_tokenize = tokenize
1588del tokenize
1589
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001590class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001591
1592 # .run() chews over the Text widget, looking for a block opener
1593 # and the stmt following it. Returns a pair,
1594 # (line containing block opener, line containing stmt)
1595 # Either or both may be None.
1596
1597 def __init__(self, text, tabwidth):
1598 self.text = text
1599 self.tabwidth = tabwidth
1600 self.i = self.finished = 0
1601 self.blkopenline = self.indentedline = None
1602
1603 def readline(self):
1604 if self.finished:
1605 return ""
1606 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001607 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001608 if self.text.compare(mark, ">=", "end"):
1609 return ""
1610 return self.text.get(mark, mark + " lineend+1c")
1611
1612 def tokeneater(self, type, token, start, end, line,
1613 INDENT=_tokenize.INDENT,
1614 NAME=_tokenize.NAME,
1615 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1616 if self.finished:
1617 pass
1618 elif type == NAME and token in OPENERS:
1619 self.blkopenline = line
1620 elif type == INDENT and self.blkopenline:
1621 self.indentedline = line
1622 self.finished = 1
1623
1624 def run(self):
1625 save_tabsize = _tokenize.tabsize
1626 _tokenize.tabsize = self.tabwidth
1627 try:
1628 try:
1629 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001630 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001631 # since we cut off the tokenizer early, we can trigger
1632 # spurious errors
1633 pass
1634 finally:
1635 _tokenize.tabsize = save_tabsize
1636 return self.blkopenline, self.indentedline
1637
1638### end autoindent code ###
1639
David Scherer7aced172000-08-15 01:13:23 +00001640def prepstr(s):
1641 # Helper to extract the underscore from a string, e.g.
1642 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001643 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001644 if i >= 0:
1645 s = s[:i] + s[i+1:]
1646 return i, s
1647
1648
1649keynames = {
1650 'bracketleft': '[',
1651 'bracketright': ']',
1652 'slash': '/',
1653}
1654
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001655def get_accelerator(keydefs, eventname):
1656 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001657 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1658 # if not keylist:
Ned Deily57847df2014-03-27 20:47:04 -07001659 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily60651532011-01-31 00:52:49 +00001660 "<<open-module>>",
1661 "<<goto-line>>",
1662 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001663 return ""
1664 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001665 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001666 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1667 s = re.sub("Key-", "", s)
1668 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1669 s = re.sub("Control-", "Ctrl-", s)
1670 s = re.sub("-", "+", s)
1671 s = re.sub("><", " ", s)
1672 s = re.sub("<", "", s)
1673 s = re.sub(">", "", s)
1674 return s
1675
1676
1677def fixwordbreaks(root):
1678 # Make sure that Tk's double-click and next/previous word
1679 # operations use our definition of a word (i.e. an identifier)
1680 tk = root.tk
1681 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1682 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1683 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1684
1685
Terry Jan Reedycf834762014-10-17 01:31:29 -04001686def _editor_window(parent): # htest #
1687 # error if close master window first - timer event, after script
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001688 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001689 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001690 if sys.argv[1:]:
1691 filename = sys.argv[1]
1692 else:
1693 filename = None
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001694 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001695 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001696 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycf834762014-10-17 01:31:29 -04001697 # Does not stop error, neither does following
1698 # edit.text.bind("<<close-window>>", edit.close_event)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001699
David Scherer7aced172000-08-15 01:13:23 +00001700
1701if __name__ == '__main__':
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001702 from idlelib.idle_test.htest import run
Terry Jan Reedy70e763c2015-09-20 19:55:44 -04001703 run(_editor_window)