blob: 0c5b7139ded393ec2043e72c8a15e88a733fc050 [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',
Terry Jan Reedy3d096222015-11-16 07:32:19 -0500187 'highlightthickness': 0,
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000188 'width': self.width,
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200189 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')}
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000190 if TkVersion >= 8.5:
191 # Starting with tk 8.5 we have to set the new tabstyle option
192 # to 'wordprocessor' to achieve the same display of tabs as in
193 # older tk versions.
194 text_options['tabstyle'] = 'wordprocessor'
195 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000196 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000197
198 self.createmenubar()
199 self.apply_bindings()
200
201 self.top.protocol("WM_DELETE_WINDOW", self.close)
202 self.top.bind("<<close-window>>", self.close_event)
Ned Deily57847df2014-03-27 20:47:04 -0700203 if macosxSupport.isAquaTk():
Ronald Oussoren17db4952006-07-23 09:41:09 +0000204 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000205 text.bind('<<close-window>>', self.close_event)
Terry Jan Reedyca33d562015-09-22 21:10:22 -0400206 # Some OS X systems have only one mouse button, so use
207 # control-click for popup context menus there. For two
208 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murray3f752ab2010-12-18 17:22:18 +0000209 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedyca33d562015-09-22 21:10:22 -0400210 text.bind("<2>", self.right_menu_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000211 else:
Terry Jan Reedyca33d562015-09-22 21:10:22 -0400212 # Elsewhere, use right-click for popup menus.
R. David Murray3f752ab2010-12-18 17:22:18 +0000213 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000214 text.bind("<<cut>>", self.cut)
215 text.bind("<<copy>>", self.copy)
216 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000217 text.bind("<<center-insert>>", self.center_insert_event)
218 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000219 text.bind("<<python-docs>>", self.python_docs)
220 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000221 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000222 text.bind("<<open-module>>", self.open_module)
223 text.bind("<<do-nothing>>", lambda event: "break")
224 text.bind("<<select-all>>", self.select_all)
225 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000226 text.bind("<<find>>", self.find_event)
227 text.bind("<<find-again>>", self.find_again_event)
228 text.bind("<<find-in-files>>", self.find_in_files_event)
229 text.bind("<<find-selection>>", self.find_selection_event)
230 text.bind("<<replace>>", self.replace_event)
231 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000232 text.bind("<<smart-backspace>>",self.smart_backspace_event)
233 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
234 text.bind("<<smart-indent>>",self.smart_indent_event)
235 text.bind("<<indent-region>>",self.indent_region_event)
236 text.bind("<<dedent-region>>",self.dedent_region_event)
237 text.bind("<<comment-region>>",self.comment_region_event)
238 text.bind("<<uncomment-region>>",self.uncomment_region_event)
239 text.bind("<<tabify-region>>",self.tabify_region_event)
240 text.bind("<<untabify-region>>",self.untabify_region_event)
241 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
242 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000243 text.bind("<Left>", self.move_at_edge_if_selection(0))
244 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000245 text.bind("<<del-word-left>>", self.del_word_left)
246 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000247 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000248
David Scherer7aced172000-08-15 01:13:23 +0000249 if flist:
250 flist.inversedict[self] = key
251 if key:
252 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000253 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000254 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
255 text.bind("<<open-class-browser>>", self.open_class_browser)
256 text.bind("<<open-path-browser>>", self.open_path_browser)
257
Steven M. Gava898a3652001-10-07 11:10:44 +0000258 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000259 vbar['command'] = text.yview
260 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000261 text['yscrollcommand'] = vbar.set
Terry Jan Reedy12352142015-08-01 18:57:27 -0400262 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000263 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
264 text.pack(side=TOP, fill=BOTH, expand=1)
265 text.focus_set()
266
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000267 # usetabs true -> literal tab characters are used by indent and
268 # dedent cmds, possibly mixed with spaces if
269 # indentwidth is not a multiple of tabwidth,
270 # which will cause Tabnanny to nag!
271 # false -> tab characters are converted to spaces by indent
272 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000273 # Although use-spaces=0 can be configured manually in config-main.def,
274 # configuration of tabs v. spaces is not supported in the configuration
275 # dialog. IDLE promotes the preferred Python indentation: use spaces!
276 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
277 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000278
279 # tabwidth is the display width of a literal tab character.
280 # CAUTION: telling Tk to use anything other than its default
281 # tab setting causes it to use an entirely different tabbing algorithm,
282 # treating tab stops as fixed distances from the left margin.
283 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000284 self.tabwidth = 8 # must remain 8 until Tk is fixed.
285
286 # indentwidth is the number of screen characters per indent level.
287 # The recommended Python indentation is four spaces.
288 self.indentwidth = self.tabwidth
289 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000290
291 # If context_use_ps1 is true, parsing searches back for a ps1 line;
292 # else searches for a popular (if, def, ...) Python stmt.
293 self.context_use_ps1 = False
294
295 # When searching backwards for a reliable place to begin parsing,
296 # first start num_context_lines[0] lines back, then
297 # num_context_lines[1] lines back if that didn't work, and so on.
298 # The last value should be huge (larger than the # of lines in a
299 # conceivable file).
300 # Making the initial values larger slows things down more often.
301 self.num_context_lines = 50, 500, 5000000
302
David Scherer7aced172000-08-15 01:13:23 +0000303 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000304
305 self.undo = undo = self.UndoDelegator()
306 per.insertfilter(undo)
307 text.undo_block_start = undo.undo_block_start
308 text.undo_block_stop = undo.undo_block_stop
309 undo.set_saved_change_hook(self.saved_change_hook)
310
311 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000312 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000313 io.set_filename_change_hook(self.filename_change_hook)
314
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000315 # Create the recent files submenu
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400316 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000317 self.menudict['file'].insert_cascade(3, label='Recent Files',
318 underline=0,
319 menu=self.recent_files_menu)
320 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000321
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000322 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000323 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000324 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000325 io.loadfile(filename)
326 else:
327 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000328 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000329 self.saved_change_hook()
330
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000331 self.set_indentation_params(self.ispythonsource(filename))
332
David Scherer7aced172000-08-15 01:13:23 +0000333 self.load_extensions()
334
335 menu = self.menudict.get('windows')
336 if menu:
337 end = menu.index("end")
338 if end is None:
339 end = -1
340 if end >= 0:
341 menu.add_separator()
342 end = end + 1
343 self.wmenu_end = end
344 WindowList.register_callback(self.postwindowsmenu)
345
346 # Some abstractions so IDLE extensions are cross-IDE
347 self.askyesno = tkMessageBox.askyesno
348 self.askinteger = tkSimpleDialog.askinteger
349 self.showerror = tkMessageBox.showerror
350
Martin v. Löwis307021f2005-11-27 16:59:04 +0000351 def _filename_to_unicode(self, filename):
352 """convert filename to unicode in order to display it in Tk"""
353 if isinstance(filename, unicode) or not filename:
354 return filename
355 else:
356 try:
357 return filename.decode(self.filesystemencoding)
358 except UnicodeDecodeError:
359 # XXX
360 try:
361 return filename.decode(self.encoding)
362 except UnicodeDecodeError:
363 # byte-to-byte conversion
364 return filename.decode('iso8859-1')
365
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000366 def new_callback(self, event):
367 dirname, basename = self.io.defaultfilename()
368 self.flist.new(dirname)
369 return "break"
370
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000371 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400372 if (event.state & 4) != 0 and event.keysym == "Home":
373 # state&4==Control. If <Control-Home>, use the Tk binding.
374 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000375 if self.text.index("iomark") and \
376 self.text.compare("iomark", "<=", "insert lineend") and \
377 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400378 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000379 insertpt = int(self.text.index("iomark").split(".")[1])
380 else:
381 line = self.text.get("insert linestart", "insert lineend")
382 for insertpt in xrange(len(line)):
383 if line[insertpt] not in (' ','\t'):
384 break
385 else:
386 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000387 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000388 if insertpt == lineat:
389 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000390 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000391 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400392 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000393 self.text.tag_remove("sel", "1.0", "end")
394 else:
395 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400396 self.text.mark_set("my_anchor", "insert") # there was no previous selection
397 else:
398 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
399 self.text.mark_set("my_anchor", "sel.first") # extend back
400 else:
401 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000402 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400403 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000404 if self.text.compare(first,">",last):
405 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000406 self.text.tag_remove("sel", "1.0", "end")
407 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000408 self.text.mark_set("insert", dest)
409 self.text.see("insert")
410 return "break"
411
David Scherer7aced172000-08-15 01:13:23 +0000412 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000413 self.status_bar = self.MultiStatusBar(self.top)
Terry Jan Reedy3d096222015-11-16 07:32:19 -0500414 sep = Frame(self.top, height=1, borderwidth=1, background='grey75')
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)
Terry Jan Reedy3d096222015-11-16 07:32:19 -0500422 sep.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000423 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
424 self.text.event_add("<<set-line-and-column>>",
425 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000426 self.text.after_idle(self.set_line_and_column)
427
428 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000429 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000430 self.status_bar.set_label('column', 'Col: %s' % column)
431 self.status_bar.set_label('line', 'Ln: %s' % line)
432
David Scherer7aced172000-08-15 01:13:23 +0000433 menu_specs = [
434 ("file", "_File"),
435 ("edit", "_Edit"),
436 ("format", "F_ormat"),
437 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000438 ("options", "_Options"),
Ned Deilyb5daa3d2015-01-17 21:03:41 -0800439 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000440 ("help", "_Help"),
441 ]
442
Ronald Oussoren19302d92006-06-11 14:33:36 +0000443
David Scherer7aced172000-08-15 01:13:23 +0000444 def createmenubar(self):
445 mbar = self.menubar
446 self.menudict = menudict = {}
447 for name, label in self.menu_specs:
448 underline, label = prepstr(label)
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400449 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000450 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000451
Ned Deily57847df2014-03-27 20:47:04 -0700452 if macosxSupport.isCarbonTk():
Ronald Oussoren19302d92006-06-11 14:33:36 +0000453 # Insert the application menu
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400454 menudict['application'] = menu = Menu(mbar, name='apple',
455 tearoff=0)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000456 mbar.add_cascade(label='IDLE', menu=menu)
457
David Scherer7aced172000-08-15 01:13:23 +0000458 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000459 self.base_helpmenu_length = self.menudict['help'].index(END)
460 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000461
462 def postwindowsmenu(self):
463 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000464 menu = self.menudict['windows']
465 end = menu.index("end")
466 if end is None:
467 end = -1
468 if end > self.wmenu_end:
469 menu.delete(self.wmenu_end+1, end)
470 WindowList.add_windows_to_menu(menu)
471
472 rmenu = None
473
474 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000475 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
476 if not self.rmenu:
477 self.make_rmenu()
478 rmenu = self.rmenu
479 self.event = event
480 iswin = sys.platform[:3] == 'win'
481 if iswin:
482 self.text.config(cursor="arrow")
Andrew Svetlov5018db72012-11-01 22:39:14 +0200483
Roger Serwy231a8fd2013-04-07 12:15:52 -0500484 for item in self.rmenu_specs:
485 try:
486 label, eventname, verify_state = item
487 except ValueError: # see issue1207589
488 continue
489
Andrew Svetlov5018db72012-11-01 22:39:14 +0200490 if verify_state is None:
491 continue
492 state = getattr(self, verify_state)()
493 rmenu.entryconfigure(label, state=state)
494
David Scherer7aced172000-08-15 01:13:23 +0000495 rmenu.tk_popup(event.x_root, event.y_root)
496 if iswin:
497 self.text.config(cursor="ibeam")
498
499 rmenu_specs = [
Andrew Svetlov5018db72012-11-01 22:39:14 +0200500 # ("Label", "<<virtual-event>>", "statefuncname"), ...
501 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000502 ]
503
504 def make_rmenu(self):
505 rmenu = Menu(self.text, tearoff=0)
Roger Serwy231a8fd2013-04-07 12:15:52 -0500506 for item in self.rmenu_specs:
507 label, eventname = item[0], item[1]
Andrew Svetlov5018db72012-11-01 22:39:14 +0200508 if label is not None:
509 def command(text=self.text, eventname=eventname):
510 text.event_generate(eventname)
511 rmenu.add_command(label=label, command=command)
512 else:
513 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000514 self.rmenu = rmenu
515
Andrew Svetlov5018db72012-11-01 22:39:14 +0200516 def rmenu_check_cut(self):
517 return self.rmenu_check_copy()
518
519 def rmenu_check_copy(self):
520 try:
521 indx = self.text.index('sel.first')
522 except TclError:
523 return 'disabled'
524 else:
525 return 'normal' if indx else 'disabled'
526
527 def rmenu_check_paste(self):
528 try:
529 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
530 except TclError:
531 return 'disabled'
532 else:
533 return 'normal'
534
David Scherer7aced172000-08-15 01:13:23 +0000535 def about_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400536 "Handle Help 'About IDLE' event."
537 # Synchronize with macosxSupport.overrideRootMenu.about_dialog.
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000538 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000539
Steven M. Gava3b55a892001-11-21 05:56:26 +0000540 def config_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400541 "Handle Options 'Configure IDLE' event."
542 # Synchronize with macosxSupport.overrideRootMenu.config_dialog.
Steven M. Gava3b55a892001-11-21 05:56:26 +0000543 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400544
David Scherer7aced172000-08-15 01:13:23 +0000545 def help_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400546 "Handle Help 'IDLE Help' event."
547 # Synchronize with macosxSupport.overrideRootMenu.help_dialog.
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500548 if self.root:
549 parent = self.root
550 else:
551 parent = self.top
Terry Jan Reedy70e763c2015-09-20 19:55:44 -0400552 help.show_idlehelp(parent)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000553
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000554 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000555 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000556 try:
557 os.startfile(self.help_url)
558 except WindowsError as why:
559 tkMessageBox.showerror(title='Document Start Failure',
560 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000561 else:
562 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000563 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000564
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000565 def cut(self,event):
566 self.text.event_generate("<<Cut>>")
567 return "break"
568
569 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000570 if not self.text.tag_ranges("sel"):
571 # There is no selection, so do nothing and maybe interrupt.
572 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000573 self.text.event_generate("<<Copy>>")
574 return "break"
575
576 def paste(self,event):
577 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000578 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000579 return "break"
580
David Scherer7aced172000-08-15 01:13:23 +0000581 def select_all(self, event=None):
582 self.text.tag_add("sel", "1.0", "end-1c")
583 self.text.mark_set("insert", "1.0")
584 self.text.see("insert")
585 return "break"
586
587 def remove_selection(self, event=None):
588 self.text.tag_remove("sel", "1.0", "end")
589 self.text.see("insert")
590
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000591 def move_at_edge_if_selection(self, edge_index):
592 """Cursor move begins at start or end of selection
593
594 When a left/right cursor key is pressed create and return to Tkinter a
595 function which causes a cursor move from the associated edge of the
596 selection.
597
598 """
599 self_text_index = self.text.index
600 self_text_mark_set = self.text.mark_set
601 edges_table = ("sel.first+1c", "sel.last-1c")
602 def move_at_edge(event):
603 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
604 try:
605 self_text_index("sel.first")
606 self_text_mark_set("insert", edges_table[edge_index])
607 except TclError:
608 pass
609 return move_at_edge
610
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000611 def del_word_left(self, event):
612 self.text.event_generate('<Meta-Delete>')
613 return "break"
614
615 def del_word_right(self, event):
616 self.text.event_generate('<Meta-d>')
617 return "break"
618
Steven M. Gavac5976402002-01-04 03:06:08 +0000619 def find_event(self, event):
620 SearchDialog.find(self.text)
621 return "break"
622
623 def find_again_event(self, event):
624 SearchDialog.find_again(self.text)
625 return "break"
626
627 def find_selection_event(self, event):
628 SearchDialog.find_selection(self.text)
629 return "break"
630
631 def find_in_files_event(self, event):
632 GrepDialog.grep(self.text, self.io, self.flist)
633 return "break"
634
635 def replace_event(self, event):
636 ReplaceDialog.replace(self.text)
637 return "break"
638
639 def goto_line_event(self, event):
640 text = self.text
641 lineno = tkSimpleDialog.askinteger("Goto",
642 "Go to line number:",parent=text)
643 if lineno is None:
644 return "break"
645 if lineno <= 0:
646 text.bell()
647 return "break"
648 text.mark_set("insert", "%d.0" % lineno)
649 text.see("insert")
650
David Scherer7aced172000-08-15 01:13:23 +0000651 def open_module(self, event=None):
652 # XXX Shouldn't this be in IOBinding or in FileList?
653 try:
654 name = self.text.get("sel.first", "sel.last")
655 except TclError:
656 name = ""
657 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000658 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000659 name = tkSimpleDialog.askstring("Module",
660 "Enter the name of a Python module\n"
661 "to search on sys.path and open:",
662 parent=self.text, initialvalue=name)
663 if name:
664 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000665 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000666 return
David Scherer7aced172000-08-15 01:13:23 +0000667 # XXX Ought to insert current file's directory in front of path
668 try:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400669 (f, file_path, (suffix, mode, mtype)) = _find_module(name)
Terry Jan Reedy2b149862013-06-29 00:59:34 -0400670 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000671 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
672 return
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400673 if mtype != imp.PY_SOURCE:
David Scherer7aced172000-08-15 01:13:23 +0000674 tkMessageBox.showerror("Unsupported type",
675 "%s is not a source module" % name, parent=self.text)
676 return
677 if f:
678 f.close()
679 if self.flist:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400680 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000681 else:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400682 self.io.loadfile(file_path)
683 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000684
685 def open_class_browser(self, event=None):
686 filename = self.io.filename
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400687 if not (self.__class__.__name__ == 'PyShellEditorWindow'
688 and filename):
689 filename = self.open_module()
690 if filename is None:
691 return
David Scherer7aced172000-08-15 01:13:23 +0000692 head, tail = os.path.split(filename)
693 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000694 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000695 ClassBrowser.ClassBrowser(self.flist, base, [head])
696
697 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000698 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000699 PathBrowser.PathBrowser(self.flist)
700
701 def gotoline(self, lineno):
702 if lineno is not None and lineno > 0:
703 self.text.mark_set("insert", "%d.0" % lineno)
704 self.text.tag_remove("sel", "1.0", "end")
705 self.text.tag_add("sel", "insert", "insert +1l")
706 self.center()
707
708 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000709 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000710 return True
David Scherer7aced172000-08-15 01:13:23 +0000711 base, ext = os.path.splitext(os.path.basename(filename))
712 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000713 return True
David Scherer7aced172000-08-15 01:13:23 +0000714 try:
715 f = open(filename)
716 line = f.readline()
717 f.close()
718 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000719 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000720 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000721
722 def close_hook(self):
723 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000724 self.flist.unregister_maybe_terminate(self)
725 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000726
727 def set_close_hook(self, close_hook):
728 self.close_hook = close_hook
729
730 def filename_change_hook(self):
731 if self.flist:
732 self.flist.filename_changed_edit(self)
733 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000734 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000735 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000736
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000737 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000738 if self.color:
739 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000740 if self.ispythonsource(self.io.filename):
741 self.color = self.ColorDelegator()
742 # can add more colorizers here...
743 if self.color:
744 self.per.removefilter(self.undo)
745 self.per.insertfilter(self.color)
746 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000747
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000748 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000749 if not self.color:
750 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000751 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000752 self.per.removefilter(self.color)
753 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000754
Steven M. Gavab77d3432002-03-02 07:16:21 +0000755 def ResetColorizer(self):
Terry Jan Reedy13755382014-10-09 18:44:26 -0400756 "Update the color theme"
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000757 # Called from self.filename_change_hook and from configDialog.py
758 self._rmcolorizer()
759 self._addcolorizer()
Terry Jan Reedy35aa5d02015-11-12 15:02:50 -0500760 theme = idleConf.CurrentTheme()
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000761 normal_colors = idleConf.GetHighlight(theme, 'normal')
762 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
763 select_colors = idleConf.GetHighlight(theme, 'hilite')
764 self.text.config(
765 foreground=normal_colors['foreground'],
766 background=normal_colors['background'],
767 insertbackground=cursor_color,
768 selectforeground=select_colors['foreground'],
769 selectbackground=select_colors['background'],
770 )
Terry Jan Reedya6673802015-09-28 04:52:44 -0400771 if TkVersion >= 8.5:
772 self.text.config(
773 inactiveselectbackground=select_colors['background'])
David Scherer7aced172000-08-15 01:13:23 +0000774
Steven M. Gavab1585412002-03-12 00:21:56 +0000775 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000776 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000777 # Called from configDialog.py
Terry Jan Reedy12352142015-08-01 18:57:27 -0400778
779 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000780
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000781 def RemoveKeybindings(self):
782 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000783 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000784 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000785 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000786 self.text.event_delete(event, *keylist)
787 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000788 xkeydefs = idleConf.GetExtensionBindings(extensionName)
789 if xkeydefs:
790 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000791 self.text.event_delete(event, *keylist)
792
793 def ApplyKeybindings(self):
794 "Update the keybindings after they are changed"
795 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000796 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000797 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000798 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000799 xkeydefs = idleConf.GetExtensionBindings(extensionName)
800 if xkeydefs:
801 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000802 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000803 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000804 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000805 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000806 for item in menu[1]:
807 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000808 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000809 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000810 menu = self.menudict[menubarItem]
Ned Deily14ef0c82013-07-20 14:38:24 -0700811 end = menu.index(END)
812 if end is None:
813 # Skip empty menus
814 continue
815 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000816 for index in range(0, end):
817 if menu.type(index) == 'command':
818 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000819 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000820 itemName = menu.entrycget(index, 'label')
821 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000822 if menubarItem in menuEventDict:
823 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000824 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000825 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000826 accel = get_accelerator(keydefs, event)
827 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000828
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000829 def set_notabs_indentwidth(self):
830 "Update the indentwidth if changed and not using tabs in this window"
831 # Called from configDialog.py
832 if not self.usetabs:
833 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
834 type='int')
835
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000836 def reset_help_menu_entries(self):
837 "Update the additional help entries on the Help menu"
838 help_list = idleConf.GetAllExtraHelpSourcesList()
839 helpmenu = self.menudict['help']
840 # first delete the extra help entries, if any
841 helpmenu_length = helpmenu.index(END)
842 if helpmenu_length > self.base_helpmenu_length:
843 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
844 # then rebuild them
845 if help_list:
846 helpmenu.add_separator()
847 for entry in help_list:
848 cmd = self.__extra_help_callback(entry[1])
849 helpmenu.add_command(label=entry[0], command=cmd)
850 # and update the menu dictionary
851 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000852
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000853 def __extra_help_callback(self, helpfile):
854 "Create a callback with the helpfile value frozen at definition time"
855 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000856 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000857 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000858 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000859 try:
860 os.startfile(helpfile)
861 except WindowsError as why:
862 tkMessageBox.showerror(title='Document Start Failure',
863 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000864 else:
865 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000866 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000867
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000868 def update_recent_files_list(self, new_file=None):
869 "Load and update the recent files list and menus"
870 rf_list = []
871 if os.path.exists(self.recent_files_path):
Terry Jan Reedyf9489432013-08-04 15:39:56 -0400872 with open(self.recent_files_path, 'r') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000873 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000874 if new_file:
875 new_file = os.path.abspath(new_file) + '\n'
876 if new_file in rf_list:
877 rf_list.remove(new_file) # move to top
878 rf_list.insert(0, new_file)
879 # clean and save the recent files list
880 bad_paths = []
881 for path in rf_list:
882 if '\0' in path or not os.path.exists(path[0:-1]):
883 bad_paths.append(path)
884 rf_list = [path for path in rf_list if path not in bad_paths]
885 ulchars = "1234567890ABCDEFGHIJK"
886 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000887 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800888 with open(self.recent_files_path, 'w') as rf_file:
889 rf_file.writelines(rf_list)
890 except IOError as err:
891 if not getattr(self.root, "recentfilelist_error_displayed", False):
892 self.root.recentfilelist_error_displayed = True
Terry Jan Reedy80487762015-10-27 03:37:55 -0400893 tkMessageBox.showwarning(title='IDLE Warning',
894 message="Cannot update File menu Recent Files list. "
895 "Your operating system says:\n%s\n"
896 "Select OK and IDLE will continue without updating."
Ned Deily40ad0412011-12-14 14:57:43 -0800897 % str(err),
898 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000899 # for each edit window instance, construct the recent files menu
900 for instance in self.top.instance_dict.keys():
901 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700902 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000903 for i, file_name in enumerate(rf_list):
904 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000905 # make unicode string to display non-ASCII chars correctly
906 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000907 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000908 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000909 command=callback,
910 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000911
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000912 def __recent_file_callback(self, file_name):
913 def open_recent_file(fn_closure=file_name):
914 self.io.open(editFile=fn_closure)
915 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000916
David Scherer7aced172000-08-15 01:13:23 +0000917 def saved_change_hook(self):
918 short = self.short_title()
919 long = self.long_title()
920 if short and long:
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400921 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000922 elif short:
923 title = short
924 elif long:
925 title = long
926 else:
927 title = "Untitled"
928 icon = short or long or title
929 if not self.get_saved():
930 title = "*%s*" % title
931 icon = "*%s" % icon
932 self.top.wm_title(title)
933 self.top.wm_iconname(icon)
934
935 def get_saved(self):
936 return self.undo.get_saved()
937
938 def set_saved(self, flag):
939 self.undo.set_saved(flag)
940
941 def reset_undo(self):
942 self.undo.reset_undo()
943
944 def short_title(self):
945 filename = self.io.filename
946 if filename:
947 filename = os.path.basename(filename)
Terry Jan Reedy59243652014-01-23 00:36:37 -0500948 else:
949 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000950 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400951 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000952
953 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000954 # return unicode string to display non-ASCII chars correctly
955 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000956
957 def center_insert_event(self, event):
958 self.center()
959
960 def center(self, mark="insert"):
961 text = self.text
962 top, bot = self.getwindowlines()
963 lineno = self.getlineno(mark)
964 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000965 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000966 text.yview(float(newtop))
967
968 def getwindowlines(self):
969 text = self.text
970 top = self.getlineno("@0,0")
971 bot = self.getlineno("@0,65535")
972 if top == bot and text.winfo_height() == 1:
973 # Geometry manager hasn't run yet
974 height = int(text['height'])
975 bot = top + height - 1
976 return top, bot
977
978 def getlineno(self, mark="insert"):
979 text = self.text
980 return int(float(text.index(mark)))
981
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000982 def get_geometry(self):
983 "Return (width, height, x, y)"
984 geom = self.top.wm_geometry()
985 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
986 tuple = (map(int, m.groups()))
987 return tuple
988
David Scherer7aced172000-08-15 01:13:23 +0000989 def close_event(self, event):
990 self.close()
991
992 def maybesave(self):
993 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000994 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000995 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000996 self.top.deiconify()
997 self.top.lower()
998 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000999 return self.io.maybesave()
1000
1001 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001002 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +00001003 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001004 self._close()
1005 return reply
1006
1007 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001008 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001009 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001010 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001011 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001012 self.io.close()
1013 self.io = None
1014 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001015 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001016 self.color.close(False)
1017 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001018 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001019 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001020 self.per.close()
1021 self.per = None
1022 self.top.destroy()
1023 if self.close_hook:
1024 # unless override: unregister from flist, terminate if last window
1025 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001026
1027 def load_extensions(self):
1028 self.extensions = {}
1029 self.load_standard_extensions()
1030
1031 def unload_extensions(self):
1032 for ins in self.extensions.values():
1033 if hasattr(ins, "close"):
1034 ins.close()
1035 self.extensions = {}
1036
1037 def load_standard_extensions(self):
1038 for name in self.get_standard_extension_names():
1039 try:
1040 self.load_extension(name)
1041 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001042 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001043 import traceback
1044 traceback.print_exc()
1045
1046 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001047 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001048
1049 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001050 try:
1051 mod = __import__(name, globals(), locals(), [])
1052 except ImportError:
1053 print "\nFailed to import extension: ", name
1054 return
David Scherer7aced172000-08-15 01:13:23 +00001055 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001056 keydefs = idleConf.GetExtensionBindings(name)
1057 if hasattr(cls, "menudefs"):
1058 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001059 ins = cls(self)
1060 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001061 if keydefs:
1062 self.apply_bindings(keydefs)
1063 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001064 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001065 while methodname[:1] == '<':
1066 methodname = methodname[1:]
1067 while methodname[-1:] == '>':
1068 methodname = methodname[:-1]
1069 methodname = methodname + "_event"
1070 if hasattr(ins, methodname):
1071 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001072
1073 def apply_bindings(self, keydefs=None):
1074 if keydefs is None:
1075 keydefs = self.Bindings.default_keydefs
1076 text = self.text
1077 text.keydefs = keydefs
1078 for event, keylist in keydefs.items():
1079 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001080 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001081
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001082 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001083 """Add appropriate entries to the menus and submenus
1084
1085 Menus that are absent or None in self.menudict are ignored.
1086 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001087 if menudefs is None:
1088 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001089 if keydefs is None:
1090 keydefs = self.Bindings.default_keydefs
1091 menudict = self.menudict
1092 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001093 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001094 menu = menudict.get(mname)
1095 if not menu:
1096 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001097 for entry in entrylist:
1098 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001099 menu.add_separator()
1100 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001101 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001102 checkbutton = (label[:1] == '!')
1103 if checkbutton:
1104 label = label[1:]
1105 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001106 accelerator = get_accelerator(keydefs, eventname)
1107 def command(text=text, eventname=eventname):
1108 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001109 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001110 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001111 menu.add_checkbutton(label=label, underline=underline,
1112 command=command, accelerator=accelerator,
1113 variable=var)
1114 else:
1115 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001116 command=command,
1117 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001118
1119 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001120 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001121 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001122 value = var.get()
1123 return value
1124 else:
1125 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001126
1127 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001128 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001129 if var:
1130 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001131 else:
1132 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001133
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001134 def get_var_obj(self, name, vartype=None):
1135 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001136 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001137 # create a Tkinter variable object with self.text as master:
1138 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001139 return var
1140
1141 # Tk implementations of "virtual text methods" -- each platform
1142 # reusing IDLE's support code needs to define these for its GUI's
1143 # flavor of widget.
1144
1145 # Is character at text_index in a Python string? Return 0 for
1146 # "guaranteed no", true for anything else. This info is expensive
1147 # to compute ab initio, but is probably already known by the
1148 # platform's colorizer.
1149
1150 def is_char_in_string(self, text_index):
1151 if self.color:
1152 # Return true iff colorizer hasn't (re)gotten this far
1153 # yet, or the character is tagged as being in a string
1154 return self.text.tag_prevrange("TODO", text_index) or \
1155 "STRING" in self.text.tag_names(text_index)
1156 else:
1157 # The colorizer is missing: assume the worst
1158 return 1
1159
1160 # If a selection is defined in the text widget, return (start,
1161 # end) as Tkinter text indices, otherwise return (None, None)
1162 def get_selection_indices(self):
1163 try:
1164 first = self.text.index("sel.first")
1165 last = self.text.index("sel.last")
1166 return first, last
1167 except TclError:
1168 return None, None
1169
1170 # Return the text widget's current view of what a tab stop means
1171 # (equivalent width in spaces).
1172
1173 def get_tabwidth(self):
1174 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1175 return int(current)
1176
1177 # Set the text widget's current view of what a tab stop means.
1178
1179 def set_tabwidth(self, newtabwidth):
1180 text = self.text
1181 if self.get_tabwidth() != newtabwidth:
1182 pixels = text.tk.call("font", "measure", text["font"],
1183 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001184 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001185 text.configure(tabs=pixels)
1186
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001187 # If ispythonsource and guess are true, guess a good value for
1188 # indentwidth based on file content (if possible), and if
1189 # indentwidth != tabwidth set usetabs false.
1190 # In any case, adjust the Text widget's view of what a tab
1191 # character means.
1192
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001193 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001194 if guess and ispythonsource:
1195 i = self.guess_indent()
1196 if 2 <= i <= 8:
1197 self.indentwidth = i
1198 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001199 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001200 self.set_tabwidth(self.tabwidth)
1201
1202 def smart_backspace_event(self, event):
1203 text = self.text
1204 first, last = self.get_selection_indices()
1205 if first and last:
1206 text.delete(first, last)
1207 text.mark_set("insert", first)
1208 return "break"
1209 # Delete whitespace left, until hitting a real char or closest
1210 # preceding virtual tab stop.
1211 chars = text.get("insert linestart", "insert")
1212 if chars == '':
1213 if text.compare("insert", ">", "1.0"):
1214 # easy: delete preceding newline
1215 text.delete("insert-1c")
1216 else:
1217 text.bell() # at start of buffer
1218 return "break"
1219 if chars[-1] not in " \t":
1220 # easy: delete preceding real char
1221 text.delete("insert-1c")
1222 return "break"
1223 # Ick. It may require *inserting* spaces if we back up over a
1224 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001225 tabwidth = self.tabwidth
1226 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001227 assert have > 0
1228 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001229 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001230 if self.context_use_ps1:
1231 last_line_of_prompt = sys.ps1.split('\n')[-1]
1232 else:
1233 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001234 ncharsdeleted = 0
1235 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001236 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001237 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001238 chars = chars[:-1]
1239 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001240 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001241 if have <= want or chars[-1] not in " \t":
1242 break
1243 text.undo_block_start()
1244 text.delete("insert-%dc" % ncharsdeleted, "insert")
1245 if have < want:
1246 text.insert("insert", ' ' * (want - have))
1247 text.undo_block_stop()
1248 return "break"
1249
1250 def smart_indent_event(self, event):
1251 # if intraline selection:
1252 # delete it
1253 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001254 # do indent-region
1255 # else:
1256 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001257 text = self.text
1258 first, last = self.get_selection_indices()
1259 text.undo_block_start()
1260 try:
1261 if first and last:
1262 if index2line(first) != index2line(last):
1263 return self.indent_region_event(event)
1264 text.delete(first, last)
1265 text.mark_set("insert", first)
1266 prefix = text.get("insert linestart", "insert")
1267 raw, effective = classifyws(prefix, self.tabwidth)
1268 if raw == len(prefix):
1269 # only whitespace to the left
1270 self.reindent_to(effective + self.indentwidth)
1271 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001272 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001273 if self.usetabs:
1274 pad = '\t'
1275 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001276 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001277 n = self.indentwidth
1278 pad = ' ' * (n - effective % n)
1279 text.insert("insert", pad)
1280 text.see("insert")
1281 return "break"
1282 finally:
1283 text.undo_block_stop()
1284
1285 def newline_and_indent_event(self, event):
1286 text = self.text
1287 first, last = self.get_selection_indices()
1288 text.undo_block_start()
1289 try:
1290 if first and last:
1291 text.delete(first, last)
1292 text.mark_set("insert", first)
1293 line = text.get("insert linestart", "insert")
1294 i, n = 0, len(line)
1295 while i < n and line[i] in " \t":
1296 i = i+1
1297 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001298 # the cursor is in or at leading indentation in a continuation
1299 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001300 text.insert("insert linestart", '\n')
1301 return "break"
1302 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001303 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001304 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001305 last_line_of_prompt = sys.ps1.split('\n')[-1]
1306 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001307 line = line[:-1]
1308 i = i+1
1309 if i:
1310 text.delete("insert - %d chars" % i, "insert")
1311 # strip whitespace after insert point
1312 while text.get("insert") in " \t":
1313 text.delete("insert")
1314 # start new line
1315 text.insert("insert", '\n')
1316
1317 # adjust indentation for continuations and block
1318 # open/close first need to find the last stmt
1319 lno = index2line(text.index('insert'))
1320 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001321 if not self.context_use_ps1:
1322 for context in self.num_context_lines:
1323 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001324 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001325 rawtext = text.get(startatindex, "insert")
1326 y.set_str(rawtext)
1327 bod = y.find_good_parse_start(
1328 self.context_use_ps1,
1329 self._build_char_in_string_func(startatindex))
1330 if bod is not None or startat == 1:
1331 break
1332 y.set_lo(bod or 0)
1333 else:
1334 r = text.tag_prevrange("console", "insert")
1335 if r:
1336 startatindex = r[1]
1337 else:
1338 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001339 rawtext = text.get(startatindex, "insert")
1340 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001341 y.set_lo(0)
1342
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001343 c = y.get_continuation_type()
1344 if c != PyParse.C_NONE:
1345 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001346 if c == PyParse.C_STRING_FIRST_LINE:
1347 # after the first line of a string; do not indent at all
1348 pass
1349 elif c == PyParse.C_STRING_NEXT_LINES:
1350 # inside a string which started before this line;
1351 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001352 text.insert("insert", indent)
1353 elif c == PyParse.C_BRACKET:
1354 # line up with the first (if any) element of the
1355 # last open bracket structure; else indent one
1356 # level beyond the indent of the line with the
1357 # last open bracket
1358 self.reindent_to(y.compute_bracket_indent())
1359 elif c == PyParse.C_BACKSLASH:
1360 # if more than one line in this stmt already, just
1361 # mimic the current indent; else if initial line
1362 # has a start on an assignment stmt, indent to
1363 # beyond leftmost =; else to beyond first chunk of
1364 # non-whitespace on initial line
1365 if y.get_num_lines_in_stmt() > 1:
1366 text.insert("insert", indent)
1367 else:
1368 self.reindent_to(y.compute_backslash_indent())
1369 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001370 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001371 return "break"
1372
1373 # This line starts a brand new stmt; indent relative to
1374 # indentation of initial line of closest preceding
1375 # interesting stmt.
1376 indent = y.get_base_indent_string()
1377 text.insert("insert", indent)
1378 if y.is_block_opener():
1379 self.smart_indent_event(event)
1380 elif indent and y.is_block_closer():
1381 self.smart_backspace_event(event)
1382 return "break"
1383 finally:
1384 text.see("insert")
1385 text.undo_block_stop()
1386
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001387 # Our editwin provides a is_char_in_string function that works
1388 # with a Tk text index, but PyParse only knows about offsets into
1389 # a string. This builds a function for PyParse that accepts an
1390 # offset.
1391
1392 def _build_char_in_string_func(self, startindex):
1393 def inner(offset, _startindex=startindex,
1394 _icis=self.is_char_in_string):
1395 return _icis(_startindex + "+%dc" % offset)
1396 return inner
1397
1398 def indent_region_event(self, event):
1399 head, tail, chars, lines = self.get_region()
1400 for pos in range(len(lines)):
1401 line = lines[pos]
1402 if line:
1403 raw, effective = classifyws(line, self.tabwidth)
1404 effective = effective + self.indentwidth
1405 lines[pos] = self._make_blanks(effective) + line[raw:]
1406 self.set_region(head, tail, chars, lines)
1407 return "break"
1408
1409 def dedent_region_event(self, event):
1410 head, tail, chars, lines = self.get_region()
1411 for pos in range(len(lines)):
1412 line = lines[pos]
1413 if line:
1414 raw, effective = classifyws(line, self.tabwidth)
1415 effective = max(effective - self.indentwidth, 0)
1416 lines[pos] = self._make_blanks(effective) + line[raw:]
1417 self.set_region(head, tail, chars, lines)
1418 return "break"
1419
1420 def comment_region_event(self, event):
1421 head, tail, chars, lines = self.get_region()
1422 for pos in range(len(lines) - 1):
1423 line = lines[pos]
1424 lines[pos] = '##' + line
1425 self.set_region(head, tail, chars, lines)
1426
1427 def uncomment_region_event(self, event):
1428 head, tail, chars, lines = self.get_region()
1429 for pos in range(len(lines)):
1430 line = lines[pos]
1431 if not line:
1432 continue
1433 if line[:2] == '##':
1434 line = line[2:]
1435 elif line[:1] == '#':
1436 line = line[1:]
1437 lines[pos] = line
1438 self.set_region(head, tail, chars, lines)
1439
1440 def tabify_region_event(self, event):
1441 head, tail, chars, lines = self.get_region()
1442 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001443 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001444 for pos in range(len(lines)):
1445 line = lines[pos]
1446 if line:
1447 raw, effective = classifyws(line, tabwidth)
1448 ntabs, nspaces = divmod(effective, tabwidth)
1449 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1450 self.set_region(head, tail, chars, lines)
1451
1452 def untabify_region_event(self, event):
1453 head, tail, chars, lines = self.get_region()
1454 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001455 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001456 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001457 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001458 self.set_region(head, tail, chars, lines)
1459
1460 def toggle_tabs_event(self, event):
1461 if self.askyesno(
1462 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001463 "Turn tabs " + ("on", "off")[self.usetabs] +
1464 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001465 ("will be", "remains at")[self.usetabs] + " 8." +
1466 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001467 parent=self.text):
1468 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001469 # Try to prevent inconsistent indentation.
1470 # User must change indent width manually after using tabs.
1471 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001472 return "break"
1473
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001474 # XXX this isn't bound to anything -- see tabwidth comments
1475## def change_tabwidth_event(self, event):
1476## new = self._asktabwidth()
1477## if new != self.tabwidth:
1478## self.tabwidth = new
1479## self.set_indentation_params(0, guess=0)
1480## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001481
1482 def change_indentwidth_event(self, event):
1483 new = self.askinteger(
1484 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001485 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001486 parent=self.text,
1487 initialvalue=self.indentwidth,
1488 minvalue=2,
1489 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001490 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001491 self.indentwidth = new
1492 return "break"
1493
1494 def get_region(self):
1495 text = self.text
1496 first, last = self.get_selection_indices()
1497 if first and last:
1498 head = text.index(first + " linestart")
1499 tail = text.index(last + "-1c lineend +1c")
1500 else:
1501 head = text.index("insert linestart")
1502 tail = text.index("insert lineend +1c")
1503 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001504 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001505 return head, tail, chars, lines
1506
1507 def set_region(self, head, tail, chars, lines):
1508 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001509 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001510 if newchars == chars:
1511 text.bell()
1512 return
1513 text.tag_remove("sel", "1.0", "end")
1514 text.mark_set("insert", head)
1515 text.undo_block_start()
1516 text.delete(head, tail)
1517 text.insert(head, newchars)
1518 text.undo_block_stop()
1519 text.tag_add("sel", head, "insert")
1520
1521 # Make string that displays as n leading blanks.
1522
1523 def _make_blanks(self, n):
1524 if self.usetabs:
1525 ntabs, nspaces = divmod(n, self.tabwidth)
1526 return '\t' * ntabs + ' ' * nspaces
1527 else:
1528 return ' ' * n
1529
1530 # Delete from beginning of line to insert point, then reinsert
1531 # column logical (meaning use tabs if appropriate) spaces.
1532
1533 def reindent_to(self, column):
1534 text = self.text
1535 text.undo_block_start()
1536 if text.compare("insert linestart", "!=", "insert"):
1537 text.delete("insert linestart", "insert")
1538 if column:
1539 text.insert("insert", self._make_blanks(column))
1540 text.undo_block_stop()
1541
1542 def _asktabwidth(self):
1543 return self.askinteger(
1544 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001545 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001546 parent=self.text,
1547 initialvalue=self.indentwidth,
1548 minvalue=2,
Roger Serwy75b249c2013-04-06 20:26:53 -05001549 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001550
1551 # Guess indentwidth from text content.
1552 # Return guessed indentwidth. This should not be believed unless
1553 # it's in a reasonable range (e.g., it will be 0 if no indented
1554 # blocks are found).
1555
1556 def guess_indent(self):
1557 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1558 if opener and indented:
1559 raw, indentsmall = classifyws(opener, self.tabwidth)
1560 raw, indentlarge = classifyws(indented, self.tabwidth)
1561 else:
1562 indentsmall = indentlarge = 0
1563 return indentlarge - indentsmall
1564
1565# "line.col" -> line, as an int
1566def index2line(index):
1567 return int(float(index))
1568
1569# Look at the leading whitespace in s.
1570# Return pair (# of leading ws characters,
1571# effective # of leading blanks after expanding
1572# tabs to width tabwidth)
1573
1574def classifyws(s, tabwidth):
1575 raw = effective = 0
1576 for ch in s:
1577 if ch == ' ':
1578 raw = raw + 1
1579 effective = effective + 1
1580 elif ch == '\t':
1581 raw = raw + 1
1582 effective = (effective // tabwidth + 1) * tabwidth
1583 else:
1584 break
1585 return raw, effective
1586
1587import tokenize
1588_tokenize = tokenize
1589del tokenize
1590
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001591class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001592
1593 # .run() chews over the Text widget, looking for a block opener
1594 # and the stmt following it. Returns a pair,
1595 # (line containing block opener, line containing stmt)
1596 # Either or both may be None.
1597
1598 def __init__(self, text, tabwidth):
1599 self.text = text
1600 self.tabwidth = tabwidth
1601 self.i = self.finished = 0
1602 self.blkopenline = self.indentedline = None
1603
1604 def readline(self):
1605 if self.finished:
1606 return ""
1607 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001608 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001609 if self.text.compare(mark, ">=", "end"):
1610 return ""
1611 return self.text.get(mark, mark + " lineend+1c")
1612
1613 def tokeneater(self, type, token, start, end, line,
1614 INDENT=_tokenize.INDENT,
1615 NAME=_tokenize.NAME,
1616 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1617 if self.finished:
1618 pass
1619 elif type == NAME and token in OPENERS:
1620 self.blkopenline = line
1621 elif type == INDENT and self.blkopenline:
1622 self.indentedline = line
1623 self.finished = 1
1624
1625 def run(self):
1626 save_tabsize = _tokenize.tabsize
1627 _tokenize.tabsize = self.tabwidth
1628 try:
1629 try:
1630 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001631 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001632 # since we cut off the tokenizer early, we can trigger
1633 # spurious errors
1634 pass
1635 finally:
1636 _tokenize.tabsize = save_tabsize
1637 return self.blkopenline, self.indentedline
1638
1639### end autoindent code ###
1640
David Scherer7aced172000-08-15 01:13:23 +00001641def prepstr(s):
1642 # Helper to extract the underscore from a string, e.g.
1643 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001644 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001645 if i >= 0:
1646 s = s[:i] + s[i+1:]
1647 return i, s
1648
1649
1650keynames = {
1651 'bracketleft': '[',
1652 'bracketright': ']',
1653 'slash': '/',
1654}
1655
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001656def get_accelerator(keydefs, eventname):
1657 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001658 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1659 # if not keylist:
Ned Deily57847df2014-03-27 20:47:04 -07001660 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily60651532011-01-31 00:52:49 +00001661 "<<open-module>>",
1662 "<<goto-line>>",
1663 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001664 return ""
1665 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001666 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001667 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1668 s = re.sub("Key-", "", s)
1669 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1670 s = re.sub("Control-", "Ctrl-", s)
1671 s = re.sub("-", "+", s)
1672 s = re.sub("><", " ", s)
1673 s = re.sub("<", "", s)
1674 s = re.sub(">", "", s)
1675 return s
1676
1677
1678def fixwordbreaks(root):
1679 # Make sure that Tk's double-click and next/previous word
1680 # operations use our definition of a word (i.e. an identifier)
1681 tk = root.tk
1682 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1683 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1684 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1685
1686
Terry Jan Reedycf834762014-10-17 01:31:29 -04001687def _editor_window(parent): # htest #
1688 # error if close master window first - timer event, after script
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001689 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001690 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001691 if sys.argv[1:]:
1692 filename = sys.argv[1]
1693 else:
1694 filename = None
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001695 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001696 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001697 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycf834762014-10-17 01:31:29 -04001698 # Does not stop error, neither does following
1699 # edit.text.bind("<<close-window>>", edit.close_event)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001700
David Scherer7aced172000-08-15 01:13:23 +00001701
1702if __name__ == '__main__':
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001703 from idlelib.idle_test.htest import run
Terry Jan Reedy70e763c2015-09-20 19:55:44 -04001704 run(_editor_window)