blob: 9944da3e7084c7d766c75c4bfedc291bc4f15d90 [file] [log] [blame]
Brett Cannonaef82d32012-04-14 20:44:23 -04001import importlib
Brett Cannon50793b42013-06-07 13:17:48 -04002import importlib.abc
Eric Snow6029e082014-01-25 15:32:46 -07003import importlib.util
David Scherer7aced172000-08-15 01:13:23 +00004import os
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -04005import platform
David Scherer7aced172000-08-15 01:13:23 +00006import re
Guido van Rossum33d26892007-08-05 15:29:28 +00007import string
Brett Cannonaef82d32012-04-14 20:44:23 -04008import sys
Georg Brandl14fc4272008-05-17 18:39:55 +00009from tkinter import *
10import tkinter.simpledialog as tkSimpleDialog
11import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000012import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000013import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000014
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000015from idlelib.MultiCall import MultiCallCreator
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000016from idlelib import WindowList
17from idlelib import SearchDialog
18from idlelib import GrepDialog
19from idlelib import ReplaceDialog
20from idlelib import PyParse
21from idlelib.configHandler import idleConf
22from idlelib import aboutDialog, textView, configDialog
23from idlelib import macosxSupport
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040024from idlelib import help
David Scherer7aced172000-08-15 01:13:23 +000025
26# The default tab setting for a Text widget, in average-width characters.
27TK_TABWIDTH_DEFAULT = 8
28
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -040029_py_version = ' (%s)' % platform.python_version()
30
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000031def _sphinx_version():
32 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
33 major, minor, micro, level, serial = sys.version_info
34 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020035 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000036 if level == 'candidate':
37 release += 'rc%s' % (serial,)
38 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000039 release += '%s%s' % (level[0], serial)
40 return release
41
Terry Jan Reedye91e7632012-02-05 15:14:20 -050042
43class HelpDialog(object):
44
45 def __init__(self):
46 self.parent = None # parent of help window
47 self.dlg = None # the help window iteself
48
49 def display(self, parent, near=None):
50 """ Display the help dialog.
51
52 parent - parent widget for the help window
53
54 near - a Toplevel widget (e.g. EditorWindow or PyShell)
55 to use as a reference for placing the help window
56 """
Terry Jan Reedya0ae7892015-09-22 22:59:40 -040057 import warnings as w
58 w.warn("EditorWindow.HelpDialog is no longer used by Idle.\n"
59 "It will be removed in 3.6 or later.\n"
60 "It has been replaced by private help.HelpWindow\n",
61 DeprecationWarning, stacklevel=2)
Terry Jan Reedye91e7632012-02-05 15:14:20 -050062 if self.dlg is None:
63 self.show_dialog(parent)
64 if near:
65 self.nearwindow(near)
66
67 def show_dialog(self, parent):
68 self.parent = parent
69 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
70 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
71 dlg.bind('<Destroy>', self.destroy, '+')
72
73 def nearwindow(self, near):
74 # Place the help dialog near the window specified by parent.
75 # Note - this may not reposition the window in Metacity
76 # if "/apps/metacity/general/disable_workarounds" is enabled
77 dlg = self.dlg
78 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
79 dlg.withdraw()
80 dlg.geometry("=+%d+%d" % geom)
81 dlg.deiconify()
82 dlg.lift()
83
84 def destroy(self, ev=None):
85 self.dlg = None
86 self.parent = None
87
Terry Jan Reedy96f802a2015-09-20 23:05:25 -040088helpDialog = HelpDialog() # singleton instance, no longer used
Terry Jan Reedye91e7632012-02-05 15:14:20 -050089
90
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000091class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000092 from idlelib.Percolator import Percolator
Terry Jan Reedye8a175e2016-05-29 01:40:30 -040093 from idlelib.ColorDelegator import ColorDelegator, color_config
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000094 from idlelib.UndoDelegator import UndoDelegator
95 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
96 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000097 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000098 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000099
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000100 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000101
102 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000103 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100104 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000105 if sys.platform.count('linux'):
106 # look for html docs in a couple of standard places
107 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
108 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
109 dochome = '/var/www/html/python/index.html'
110 else:
111 basepath = '/usr/share/doc/' # standard location
112 dochome = os.path.join(basepath, pyver,
113 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000114 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100115 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +0000116 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000117 if os.path.isfile(chmfile):
118 dochome = chmfile
Ned Deilyb7601672014-03-27 20:49:14 -0700119 elif sys.platform == 'darwin':
120 # documentation may be stored inside a python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100121 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000122 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000123 dochome = os.path.normpath(dochome)
124 if os.path.isfile(dochome):
125 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000126 if sys.platform == 'darwin':
127 # Safari requires real file:-URLs
128 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000129 else:
Terry Jan Reedyb6e17782014-09-19 22:54:15 -0400130 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
David Scherer7aced172000-08-15 01:13:23 +0000131 self.flist = flist
132 root = root or flist.root
133 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000134 try:
135 sys.ps1
136 except AttributeError:
137 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000138 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000139 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000140 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000141 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200142 #self.top.instance_dict makes flist.inversedict available to
143 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000144 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000145 else:
146 self.tkinter_vars = {} # keys: Tkinter event names
147 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000148 self.top.instance_dict = {}
149 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000150 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000151 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000152 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200153 self.width = idleConf.GetOption('main', 'EditorWindow',
154 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000155 text_options = {
156 'name': 'text',
157 'padx': 5,
158 'wrap': 'none',
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500159 'highlightthickness': 0,
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000160 'width': self.width,
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200161 'height': idleConf.GetOption('main', 'EditorWindow',
162 'height', type='int')}
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000163 if TkVersion >= 8.5:
164 # Starting with tk 8.5 we have to set the new tabstyle option
165 # to 'wordprocessor' to achieve the same display of tabs as in
166 # older tk versions.
167 text_options['tabstyle'] = 'wordprocessor'
168 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000169 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000170
171 self.createmenubar()
172 self.apply_bindings()
173
174 self.top.protocol("WM_DELETE_WINDOW", self.close)
175 self.top.bind("<<close-window>>", self.close_event)
Ned Deilyb7601672014-03-27 20:49:14 -0700176 if macosxSupport.isAquaTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000177 # Command-W on editorwindows doesn't work without this.
178 text.bind('<<close-window>>', self.close_event)
Terry Jan Reedy3c7eccd02015-09-22 21:10:27 -0400179 # Some OS X systems have only one mouse button, so use
180 # control-click for popup context menus there. For two
181 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000182 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedy3c7eccd02015-09-22 21:10:27 -0400183 text.bind("<2>", self.right_menu_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000184 else:
Terry Jan Reedy3c7eccd02015-09-22 21:10:27 -0400185 # Elsewhere, use right-click for popup menus.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000186 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000187 text.bind("<<cut>>", self.cut)
188 text.bind("<<copy>>", self.copy)
189 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000190 text.bind("<<center-insert>>", self.center_insert_event)
191 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000192 text.bind("<<python-docs>>", self.python_docs)
193 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000194 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000195 text.bind("<<open-module>>", self.open_module)
196 text.bind("<<do-nothing>>", lambda event: "break")
197 text.bind("<<select-all>>", self.select_all)
198 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000199 text.bind("<<find>>", self.find_event)
200 text.bind("<<find-again>>", self.find_again_event)
201 text.bind("<<find-in-files>>", self.find_in_files_event)
202 text.bind("<<find-selection>>", self.find_selection_event)
203 text.bind("<<replace>>", self.replace_event)
204 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000205 text.bind("<<smart-backspace>>",self.smart_backspace_event)
206 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
207 text.bind("<<smart-indent>>",self.smart_indent_event)
208 text.bind("<<indent-region>>",self.indent_region_event)
209 text.bind("<<dedent-region>>",self.dedent_region_event)
210 text.bind("<<comment-region>>",self.comment_region_event)
211 text.bind("<<uncomment-region>>",self.uncomment_region_event)
212 text.bind("<<tabify-region>>",self.tabify_region_event)
213 text.bind("<<untabify-region>>",self.untabify_region_event)
214 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
215 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000216 text.bind("<Left>", self.move_at_edge_if_selection(0))
217 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000218 text.bind("<<del-word-left>>", self.del_word_left)
219 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000220 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000221
David Scherer7aced172000-08-15 01:13:23 +0000222 if flist:
223 flist.inversedict[self] = key
224 if key:
225 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000226 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000227 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
228 text.bind("<<open-class-browser>>", self.open_class_browser)
229 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400230 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000231
Steven M. Gava898a3652001-10-07 11:10:44 +0000232 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000233 vbar['command'] = text.yview
234 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000235 text['yscrollcommand'] = vbar.set
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400236 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000237 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
238 text.pack(side=TOP, fill=BOTH, expand=1)
239 text.focus_set()
240
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000241 # usetabs true -> literal tab characters are used by indent and
242 # dedent cmds, possibly mixed with spaces if
243 # indentwidth is not a multiple of tabwidth,
244 # which will cause Tabnanny to nag!
245 # false -> tab characters are converted to spaces by indent
246 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000247 # Although use-spaces=0 can be configured manually in config-main.def,
248 # configuration of tabs v. spaces is not supported in the configuration
249 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200250 usespaces = idleConf.GetOption('main', 'Indent',
251 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000252 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000253
254 # tabwidth is the display width of a literal tab character.
255 # CAUTION: telling Tk to use anything other than its default
256 # tab setting causes it to use an entirely different tabbing algorithm,
257 # treating tab stops as fixed distances from the left margin.
258 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000259 self.tabwidth = 8 # must remain 8 until Tk is fixed.
260
261 # indentwidth is the number of screen characters per indent level.
262 # The recommended Python indentation is four spaces.
263 self.indentwidth = self.tabwidth
264 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000265
266 # If context_use_ps1 is true, parsing searches back for a ps1 line;
267 # else searches for a popular (if, def, ...) Python stmt.
268 self.context_use_ps1 = False
269
270 # When searching backwards for a reliable place to begin parsing,
271 # first start num_context_lines[0] lines back, then
272 # num_context_lines[1] lines back if that didn't work, and so on.
273 # The last value should be huge (larger than the # of lines in a
274 # conceivable file).
275 # Making the initial values larger slows things down more often.
276 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000277 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000278 self.undo = undo = self.UndoDelegator()
279 per.insertfilter(undo)
280 text.undo_block_start = undo.undo_block_start
281 text.undo_block_stop = undo.undo_block_stop
282 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000283 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000284 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000285 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000286 self.good_load = False
287 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000288 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000289 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000290 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000291 if io.loadfile(filename):
292 self.good_load = True
293 is_py_src = self.ispythonsource(filename)
294 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000295 else:
296 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500297 self.good_load = True
298
Christian Heimesa156e092008-02-16 07:38:31 +0000299 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000300 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000301 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000302 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000303 menu = self.menudict.get('windows')
304 if menu:
305 end = menu.index("end")
306 if end is None:
307 end = -1
308 if end >= 0:
309 menu.add_separator()
310 end = end + 1
311 self.wmenu_end = end
312 WindowList.register_callback(self.postwindowsmenu)
313
314 # Some abstractions so IDLE extensions are cross-IDE
315 self.askyesno = tkMessageBox.askyesno
316 self.askinteger = tkSimpleDialog.askinteger
317 self.showerror = tkMessageBox.showerror
318
Martin v. Löwis307021f2005-11-27 16:59:04 +0000319 def _filename_to_unicode(self, filename):
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400320 """Return filename as BMP unicode so diplayable in Tk."""
321 # Decode bytes to unicode.
322 if isinstance(filename, bytes):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000323 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400324 filename = filename.decode(self.filesystemencoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000325 except UnicodeDecodeError:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000326 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400327 filename = filename.decode(self.encoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000328 except UnicodeDecodeError:
329 # byte-to-byte conversion
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400330 filename = filename.decode('iso8859-1')
331 # Replace non-BMP char with diamond questionmark.
332 return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000333
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000334 def new_callback(self, event):
335 dirname, basename = self.io.defaultfilename()
336 self.flist.new(dirname)
337 return "break"
338
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000339 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400340 if (event.state & 4) != 0 and event.keysym == "Home":
341 # state&4==Control. If <Control-Home>, use the Tk binding.
342 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000343 if self.text.index("iomark") and \
344 self.text.compare("iomark", "<=", "insert lineend") and \
345 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400346 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000347 insertpt = int(self.text.index("iomark").split(".")[1])
348 else:
349 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000350 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000351 if line[insertpt] not in (' ','\t'):
352 break
353 else:
354 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000355 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000356 if insertpt == lineat:
357 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000358 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000359 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400360 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000361 self.text.tag_remove("sel", "1.0", "end")
362 else:
363 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200364 # there was no previous selection
365 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400366 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200367 if self.text.compare(self.text.index("sel.first"), "<",
368 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400369 self.text.mark_set("my_anchor", "sel.first") # extend back
370 else:
371 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000372 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400373 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000374 if self.text.compare(first,">",last):
375 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000376 self.text.tag_remove("sel", "1.0", "end")
377 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000378 self.text.mark_set("insert", dest)
379 self.text.see("insert")
380 return "break"
381
David Scherer7aced172000-08-15 01:13:23 +0000382 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000383 self.status_bar = self.MultiStatusBar(self.top)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500384 sep = Frame(self.top, height=1, borderwidth=1, background='grey75')
Ned Deilyb7601672014-03-27 20:49:14 -0700385 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000386 # Insert some padding to avoid obscuring some of the statusbar
387 # by the resize widget.
388 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000389 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
390 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
391 self.status_bar.pack(side=BOTTOM, fill=X)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500392 sep.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000393 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
394 self.text.event_add("<<set-line-and-column>>",
395 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000396 self.text.after_idle(self.set_line_and_column)
397
398 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000399 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000400 self.status_bar.set_label('column', 'Col: %s' % column)
401 self.status_bar.set_label('line', 'Ln: %s' % line)
402
David Scherer7aced172000-08-15 01:13:23 +0000403 menu_specs = [
404 ("file", "_File"),
405 ("edit", "_Edit"),
406 ("format", "F_ormat"),
407 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000408 ("options", "_Options"),
Ned Deilyccb416f2015-01-17 21:06:27 -0800409 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000410 ("help", "_Help"),
411 ]
412
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000413
David Scherer7aced172000-08-15 01:13:23 +0000414 def createmenubar(self):
415 mbar = self.menubar
416 self.menudict = menudict = {}
417 for name, label in self.menu_specs:
418 underline, label = prepstr(label)
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400419 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000420 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ned Deilyb7601672014-03-27 20:49:14 -0700421 if macosxSupport.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000422 # Insert the application menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400423 menudict['application'] = menu = Menu(mbar, name='apple',
424 tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000425 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000426 self.fill_menus()
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400427 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000428 self.menudict['file'].insert_cascade(3, label='Recent Files',
429 underline=0,
430 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000431 self.base_helpmenu_length = self.menudict['help'].index(END)
432 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000433
434 def postwindowsmenu(self):
435 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000436 menu = self.menudict['windows']
437 end = menu.index("end")
438 if end is None:
439 end = -1
440 if end > self.wmenu_end:
441 menu.delete(self.wmenu_end+1, end)
442 WindowList.add_windows_to_menu(menu)
443
444 rmenu = None
445
446 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000447 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
448 if not self.rmenu:
449 self.make_rmenu()
450 rmenu = self.rmenu
451 self.event = event
452 iswin = sys.platform[:3] == 'win'
453 if iswin:
454 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200455
Roger Serwy6b2918a2013-04-07 12:15:52 -0500456 for item in self.rmenu_specs:
457 try:
458 label, eventname, verify_state = item
459 except ValueError: # see issue1207589
460 continue
461
Andrew Svetlovd1837672012-11-01 22:41:19 +0200462 if verify_state is None:
463 continue
464 state = getattr(self, verify_state)()
465 rmenu.entryconfigure(label, state=state)
466
467
David Scherer7aced172000-08-15 01:13:23 +0000468 rmenu.tk_popup(event.x_root, event.y_root)
469 if iswin:
470 self.text.config(cursor="ibeam")
471
472 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200473 # ("Label", "<<virtual-event>>", "statefuncname"), ...
474 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000475 ]
476
477 def make_rmenu(self):
478 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500479 for item in self.rmenu_specs:
480 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200481 if label is not None:
482 def command(text=self.text, eventname=eventname):
483 text.event_generate(eventname)
484 rmenu.add_command(label=label, command=command)
485 else:
486 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000487 self.rmenu = rmenu
488
Andrew Svetlovd1837672012-11-01 22:41:19 +0200489 def rmenu_check_cut(self):
490 return self.rmenu_check_copy()
491
492 def rmenu_check_copy(self):
493 try:
494 indx = self.text.index('sel.first')
495 except TclError:
496 return 'disabled'
497 else:
498 return 'normal' if indx else 'disabled'
499
500 def rmenu_check_paste(self):
501 try:
502 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
503 except TclError:
504 return 'disabled'
505 else:
506 return 'normal'
507
David Scherer7aced172000-08-15 01:13:23 +0000508 def about_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400509 "Handle Help 'About IDLE' event."
510 # Synchronize with macosxSupport.overrideRootMenu.about_dialog.
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000511 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000512
Steven M. Gava3b55a892001-11-21 05:56:26 +0000513 def config_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400514 "Handle Options 'Configure IDLE' event."
515 # Synchronize with macosxSupport.overrideRootMenu.config_dialog.
Steven M. Gava3b55a892001-11-21 05:56:26 +0000516 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400517
David Scherer7aced172000-08-15 01:13:23 +0000518 def help_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400519 "Handle Help 'IDLE Help' event."
520 # Synchronize with macosxSupport.overrideRootMenu.help_dialog.
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500521 if self.root:
522 parent = self.root
523 else:
524 parent = self.top
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400525 help.show_idlehelp(parent)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000526
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000527 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000528 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000529 try:
530 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200531 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000532 tkMessageBox.showerror(title='Document Start Failure',
533 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000534 else:
535 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000536 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000537
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000538 def cut(self,event):
539 self.text.event_generate("<<Cut>>")
540 return "break"
541
542 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000543 if not self.text.tag_ranges("sel"):
544 # There is no selection, so do nothing and maybe interrupt.
545 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000546 self.text.event_generate("<<Copy>>")
547 return "break"
548
549 def paste(self,event):
550 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000551 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000552 return "break"
553
David Scherer7aced172000-08-15 01:13:23 +0000554 def select_all(self, event=None):
555 self.text.tag_add("sel", "1.0", "end-1c")
556 self.text.mark_set("insert", "1.0")
557 self.text.see("insert")
558 return "break"
559
560 def remove_selection(self, event=None):
561 self.text.tag_remove("sel", "1.0", "end")
562 self.text.see("insert")
563
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000564 def move_at_edge_if_selection(self, edge_index):
565 """Cursor move begins at start or end of selection
566
567 When a left/right cursor key is pressed create and return to Tkinter a
568 function which causes a cursor move from the associated edge of the
569 selection.
570
571 """
572 self_text_index = self.text.index
573 self_text_mark_set = self.text.mark_set
574 edges_table = ("sel.first+1c", "sel.last-1c")
575 def move_at_edge(event):
576 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
577 try:
578 self_text_index("sel.first")
579 self_text_mark_set("insert", edges_table[edge_index])
580 except TclError:
581 pass
582 return move_at_edge
583
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000584 def del_word_left(self, event):
585 self.text.event_generate('<Meta-Delete>')
586 return "break"
587
588 def del_word_right(self, event):
589 self.text.event_generate('<Meta-d>')
590 return "break"
591
Steven M. Gavac5976402002-01-04 03:06:08 +0000592 def find_event(self, event):
593 SearchDialog.find(self.text)
594 return "break"
595
596 def find_again_event(self, event):
597 SearchDialog.find_again(self.text)
598 return "break"
599
600 def find_selection_event(self, event):
601 SearchDialog.find_selection(self.text)
602 return "break"
603
604 def find_in_files_event(self, event):
605 GrepDialog.grep(self.text, self.io, self.flist)
606 return "break"
607
608 def replace_event(self, event):
609 ReplaceDialog.replace(self.text)
610 return "break"
611
612 def goto_line_event(self, event):
613 text = self.text
614 lineno = tkSimpleDialog.askinteger("Goto",
615 "Go to line number:",parent=text)
616 if lineno is None:
617 return "break"
618 if lineno <= 0:
619 text.bell()
620 return "break"
621 text.mark_set("insert", "%d.0" % lineno)
622 text.see("insert")
623
David Scherer7aced172000-08-15 01:13:23 +0000624 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000625 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000626 try:
627 name = self.text.get("sel.first", "sel.last")
628 except TclError:
629 name = ""
630 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000631 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000632 name = tkSimpleDialog.askstring("Module",
633 "Enter the name of a Python module\n"
634 "to search on sys.path and open:",
635 parent=self.text, initialvalue=name)
636 if name:
637 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000638 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000639 return
David Scherer7aced172000-08-15 01:13:23 +0000640 # XXX Ought to insert current file's directory in front of path
641 try:
Eric Snow6029e082014-01-25 15:32:46 -0700642 spec = importlib.util.find_spec(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400643 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000644 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
645 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700646 if spec is None:
Brett Cannon50793b42013-06-07 13:17:48 -0400647 tkMessageBox.showerror("Import error", "module not found",
648 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000649 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700650 if not isinstance(spec.loader, importlib.abc.SourceLoader):
Brett Cannon50793b42013-06-07 13:17:48 -0400651 tkMessageBox.showerror("Import error", "not a source-based module",
652 parent=self.text)
653 return
654 try:
Eric Snow02b9f9d2014-01-06 20:42:59 -0700655 file_path = spec.loader.get_filename(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400656 except AttributeError:
657 tkMessageBox.showerror("Import error",
658 "loader does not support get_filename",
659 parent=self.text)
660 return
David Scherer7aced172000-08-15 01:13:23 +0000661 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400662 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000663 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400664 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400665 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000666
667 def open_class_browser(self, event=None):
668 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400669 if not (self.__class__.__name__ == 'PyShellEditorWindow'
670 and filename):
671 filename = self.open_module()
672 if filename is None:
673 return
David Scherer7aced172000-08-15 01:13:23 +0000674 head, tail = os.path.split(filename)
675 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000676 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000677 ClassBrowser.ClassBrowser(self.flist, base, [head])
678
679 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000680 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000681 PathBrowser.PathBrowser(self.flist)
682
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400683 def open_turtle_demo(self, event = None):
684 import subprocess
685
686 cmd = [sys.executable,
687 '-c',
688 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400689 subprocess.Popen(cmd, shell=False)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400690
David Scherer7aced172000-08-15 01:13:23 +0000691 def gotoline(self, lineno):
692 if lineno is not None and lineno > 0:
693 self.text.mark_set("insert", "%d.0" % lineno)
694 self.text.tag_remove("sel", "1.0", "end")
695 self.text.tag_add("sel", "insert", "insert +1l")
696 self.center()
697
698 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000699 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000700 return True
David Scherer7aced172000-08-15 01:13:23 +0000701 base, ext = os.path.splitext(os.path.basename(filename))
702 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000703 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000704 line = self.text.get('1.0', '1.0 lineend')
705 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000706
707 def close_hook(self):
708 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000709 self.flist.unregister_maybe_terminate(self)
710 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000711
712 def set_close_hook(self, close_hook):
713 self.close_hook = close_hook
714
715 def filename_change_hook(self):
716 if self.flist:
717 self.flist.filename_changed_edit(self)
718 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000719 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000720 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000721
Christian Heimesa156e092008-02-16 07:38:31 +0000722 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000723 if self.color:
724 return
Christian Heimesa156e092008-02-16 07:38:31 +0000725 if self.ispythonsource(self.io.filename):
726 self.color = self.ColorDelegator()
727 # can add more colorizers here...
728 if self.color:
729 self.per.removefilter(self.undo)
730 self.per.insertfilter(self.color)
731 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000732
Christian Heimesa156e092008-02-16 07:38:31 +0000733 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000734 if not self.color:
735 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000736 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000737 self.per.removefilter(self.color)
738 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000739
Steven M. Gavab77d3432002-03-02 07:16:21 +0000740 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400741 "Update the color theme"
Christian Heimesa156e092008-02-16 07:38:31 +0000742 # Called from self.filename_change_hook and from configDialog.py
743 self._rmcolorizer()
744 self._addcolorizer()
Terry Jan Reedye8a175e2016-05-29 01:40:30 -0400745 EditorWindow.color_config(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000746
Guido van Rossum33d26892007-08-05 15:29:28 +0000747 IDENTCHARS = string.ascii_letters + string.digits + "_"
748
749 def colorize_syntax_error(self, text, pos):
750 text.tag_add("ERROR", pos)
751 char = text.get(pos)
752 if char and char in self.IDENTCHARS:
753 text.tag_add("ERROR", pos + " wordstart", pos)
754 if '\n' == text.get(pos): # error at line end
755 text.mark_set("insert", pos)
756 else:
757 text.mark_set("insert", pos + "+1c")
758 text.see(pos)
759
Steven M. Gavab1585412002-03-12 00:21:56 +0000760 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000761 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000762 # Called from configDialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400763
764 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000765
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000766 def RemoveKeybindings(self):
767 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000768 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000769 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000770 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000771 self.text.event_delete(event, *keylist)
772 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000773 xkeydefs = idleConf.GetExtensionBindings(extensionName)
774 if xkeydefs:
775 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000776 self.text.event_delete(event, *keylist)
777
778 def ApplyKeybindings(self):
779 "Update the keybindings after they are changed"
780 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000781 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000782 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000783 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000784 xkeydefs = idleConf.GetExtensionBindings(extensionName)
785 if xkeydefs:
786 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000787 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000788 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000789 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000790 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000791 for item in menu[1]:
792 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000793 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000794 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000795 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700796 end = menu.index(END)
797 if end is None:
798 # Skip empty menus
799 continue
800 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000801 for index in range(0, end):
802 if menu.type(index) == 'command':
803 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000804 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000805 itemName = menu.entrycget(index, 'label')
806 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000807 if menubarItem in menuEventDict:
808 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000809 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000810 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000811 accel = get_accelerator(keydefs, event)
812 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000813
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000814 def set_notabs_indentwidth(self):
815 "Update the indentwidth if changed and not using tabs in this window"
816 # Called from configDialog.py
817 if not self.usetabs:
818 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
819 type='int')
820
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000821 def reset_help_menu_entries(self):
822 "Update the additional help entries on the Help menu"
823 help_list = idleConf.GetAllExtraHelpSourcesList()
824 helpmenu = self.menudict['help']
825 # first delete the extra help entries, if any
826 helpmenu_length = helpmenu.index(END)
827 if helpmenu_length > self.base_helpmenu_length:
828 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
829 # then rebuild them
830 if help_list:
831 helpmenu.add_separator()
832 for entry in help_list:
833 cmd = self.__extra_help_callback(entry[1])
834 helpmenu.add_command(label=entry[0], command=cmd)
835 # and update the menu dictionary
836 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000837
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000838 def __extra_help_callback(self, helpfile):
839 "Create a callback with the helpfile value frozen at definition time"
840 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000841 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000842 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000843 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000844 try:
845 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200846 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000847 tkMessageBox.showerror(title='Document Start Failure',
848 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000849 else:
850 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000851 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000852
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000853 def update_recent_files_list(self, new_file=None):
854 "Load and update the recent files list and menus"
855 rf_list = []
856 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400857 with open(self.recent_files_path, 'r',
858 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000859 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000860 if new_file:
861 new_file = os.path.abspath(new_file) + '\n'
862 if new_file in rf_list:
863 rf_list.remove(new_file) # move to top
864 rf_list.insert(0, new_file)
865 # clean and save the recent files list
866 bad_paths = []
867 for path in rf_list:
868 if '\0' in path or not os.path.exists(path[0:-1]):
869 bad_paths.append(path)
870 rf_list = [path for path in rf_list if path not in bad_paths]
871 ulchars = "1234567890ABCDEFGHIJK"
872 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000873 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800874 with open(self.recent_files_path, 'w',
875 encoding='utf_8', errors='replace') as rf_file:
876 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200877 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800878 if not getattr(self.root, "recentfilelist_error_displayed", False):
879 self.root.recentfilelist_error_displayed = True
Terry Jan Reedy4c973e92015-10-27 03:38:02 -0400880 tkMessageBox.showwarning(title='IDLE Warning',
881 message="Cannot update File menu Recent Files list. "
882 "Your operating system says:\n%s\n"
883 "Select OK and IDLE will continue without updating."
884 % self._filename_to_unicode(str(err)),
Ned Deilyf505b742011-12-14 14:58:24 -0800885 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000886 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000887 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000888 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700889 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000890 for i, file_name in enumerate(rf_list):
891 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000892 # make unicode string to display non-ASCII chars correctly
893 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000894 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000895 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000896 command=callback,
897 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000898
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000899 def __recent_file_callback(self, file_name):
900 def open_recent_file(fn_closure=file_name):
901 self.io.open(editFile=fn_closure)
902 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000903
David Scherer7aced172000-08-15 01:13:23 +0000904 def saved_change_hook(self):
905 short = self.short_title()
906 long = self.long_title()
907 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400908 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000909 elif short:
910 title = short
911 elif long:
912 title = long
913 else:
914 title = "Untitled"
915 icon = short or long or title
916 if not self.get_saved():
917 title = "*%s*" % title
918 icon = "*%s" % icon
919 self.top.wm_title(title)
920 self.top.wm_iconname(icon)
921
922 def get_saved(self):
923 return self.undo.get_saved()
924
925 def set_saved(self, flag):
926 self.undo.set_saved(flag)
927
928 def reset_undo(self):
929 self.undo.reset_undo()
930
931 def short_title(self):
932 filename = self.io.filename
933 if filename:
934 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500935 else:
936 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000937 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400938 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000939
940 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000941 # return unicode string to display non-ASCII chars correctly
942 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000943
944 def center_insert_event(self, event):
945 self.center()
946
947 def center(self, mark="insert"):
948 text = self.text
949 top, bot = self.getwindowlines()
950 lineno = self.getlineno(mark)
951 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000952 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000953 text.yview(float(newtop))
954
955 def getwindowlines(self):
956 text = self.text
957 top = self.getlineno("@0,0")
958 bot = self.getlineno("@0,65535")
959 if top == bot and text.winfo_height() == 1:
960 # Geometry manager hasn't run yet
961 height = int(text['height'])
962 bot = top + height - 1
963 return top, bot
964
965 def getlineno(self, mark="insert"):
966 text = self.text
967 return int(float(text.index(mark)))
968
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000969 def get_geometry(self):
970 "Return (width, height, x, y)"
971 geom = self.top.wm_geometry()
972 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000973 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000974
David Scherer7aced172000-08-15 01:13:23 +0000975 def close_event(self, event):
976 self.close()
977
978 def maybesave(self):
979 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000980 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000981 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000982 self.top.deiconify()
983 self.top.lower()
984 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000985 return self.io.maybesave()
986
987 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000988 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000989 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000990 self._close()
991 return reply
992
993 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000994 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000995 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000996 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000997 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000998 self.io.close()
999 self.io = None
1000 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001001 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001002 self.color.close(False)
1003 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001004 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001005 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001006 self.per.close()
1007 self.per = None
1008 self.top.destroy()
1009 if self.close_hook:
1010 # unless override: unregister from flist, terminate if last window
1011 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001012
1013 def load_extensions(self):
1014 self.extensions = {}
1015 self.load_standard_extensions()
1016
1017 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001018 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001019 if hasattr(ins, "close"):
1020 ins.close()
1021 self.extensions = {}
1022
1023 def load_standard_extensions(self):
1024 for name in self.get_standard_extension_names():
1025 try:
1026 self.load_extension(name)
1027 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001028 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001029 traceback.print_exc()
1030
1031 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001032 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001033
1034 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001035 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001036 try:
1037 mod = importlib.import_module('.' + name, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001038 except (ImportError, TypeError):
Brett Cannonaef82d32012-04-14 20:44:23 -04001039 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001040 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001041 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001042 raise
David Scherer7aced172000-08-15 01:13:23 +00001043 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001044 keydefs = idleConf.GetExtensionBindings(name)
1045 if hasattr(cls, "menudefs"):
1046 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001047 ins = cls(self)
1048 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001049 if keydefs:
1050 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001051 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001052 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001053 while methodname[:1] == '<':
1054 methodname = methodname[1:]
1055 while methodname[-1:] == '>':
1056 methodname = methodname[:-1]
1057 methodname = methodname + "_event"
1058 if hasattr(ins, methodname):
1059 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001060
1061 def apply_bindings(self, keydefs=None):
1062 if keydefs is None:
1063 keydefs = self.Bindings.default_keydefs
1064 text = self.text
1065 text.keydefs = keydefs
1066 for event, keylist in keydefs.items():
1067 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001068 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001069
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001070 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001071 """Add appropriate entries to the menus and submenus
1072
1073 Menus that are absent or None in self.menudict are ignored.
1074 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001075 if menudefs is None:
1076 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001077 if keydefs is None:
1078 keydefs = self.Bindings.default_keydefs
1079 menudict = self.menudict
1080 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001081 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001082 menu = menudict.get(mname)
1083 if not menu:
1084 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001085 for entry in entrylist:
1086 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001087 menu.add_separator()
1088 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001089 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001090 checkbutton = (label[:1] == '!')
1091 if checkbutton:
1092 label = label[1:]
1093 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001094 accelerator = get_accelerator(keydefs, eventname)
1095 def command(text=text, eventname=eventname):
1096 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001097 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001098 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001099 menu.add_checkbutton(label=label, underline=underline,
1100 command=command, accelerator=accelerator,
1101 variable=var)
1102 else:
1103 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001104 command=command,
1105 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001106
1107 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001108 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001109 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001110 value = var.get()
1111 return value
1112 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001113 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001114
1115 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001116 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001117 if var:
1118 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001119 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001120 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001121
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001122 def get_var_obj(self, name, vartype=None):
1123 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001124 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001125 # create a Tkinter variable object with self.text as master:
1126 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001127 return var
1128
1129 # Tk implementations of "virtual text methods" -- each platform
1130 # reusing IDLE's support code needs to define these for its GUI's
1131 # flavor of widget.
1132
1133 # Is character at text_index in a Python string? Return 0 for
1134 # "guaranteed no", true for anything else. This info is expensive
1135 # to compute ab initio, but is probably already known by the
1136 # platform's colorizer.
1137
1138 def is_char_in_string(self, text_index):
1139 if self.color:
1140 # Return true iff colorizer hasn't (re)gotten this far
1141 # yet, or the character is tagged as being in a string
1142 return self.text.tag_prevrange("TODO", text_index) or \
1143 "STRING" in self.text.tag_names(text_index)
1144 else:
1145 # The colorizer is missing: assume the worst
1146 return 1
1147
1148 # If a selection is defined in the text widget, return (start,
1149 # end) as Tkinter text indices, otherwise return (None, None)
1150 def get_selection_indices(self):
1151 try:
1152 first = self.text.index("sel.first")
1153 last = self.text.index("sel.last")
1154 return first, last
1155 except TclError:
1156 return None, None
1157
1158 # Return the text widget's current view of what a tab stop means
1159 # (equivalent width in spaces).
1160
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001161 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001162 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1163 return int(current)
1164
1165 # Set the text widget's current view of what a tab stop means.
1166
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001167 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001168 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001169 if self.get_tk_tabwidth() != newtabwidth:
1170 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001171 pixels = text.tk.call("font", "measure", text["font"],
1172 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001173 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001174 text.configure(tabs=pixels)
1175
Guido van Rossum33d26892007-08-05 15:29:28 +00001176### begin autoindent code ### (configuration was moved to beginning of class)
1177
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001178 def set_indentation_params(self, is_py_src, guess=True):
1179 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001180 i = self.guess_indent()
1181 if 2 <= i <= 8:
1182 self.indentwidth = i
1183 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001184 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001185 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001186
1187 def smart_backspace_event(self, event):
1188 text = self.text
1189 first, last = self.get_selection_indices()
1190 if first and last:
1191 text.delete(first, last)
1192 text.mark_set("insert", first)
1193 return "break"
1194 # Delete whitespace left, until hitting a real char or closest
1195 # preceding virtual tab stop.
1196 chars = text.get("insert linestart", "insert")
1197 if chars == '':
1198 if text.compare("insert", ">", "1.0"):
1199 # easy: delete preceding newline
1200 text.delete("insert-1c")
1201 else:
1202 text.bell() # at start of buffer
1203 return "break"
1204 if chars[-1] not in " \t":
1205 # easy: delete preceding real char
1206 text.delete("insert-1c")
1207 return "break"
1208 # Ick. It may require *inserting* spaces if we back up over a
1209 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001210 tabwidth = self.tabwidth
1211 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001212 assert have > 0
1213 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001214 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001215 if self.context_use_ps1:
1216 last_line_of_prompt = sys.ps1.split('\n')[-1]
1217 else:
1218 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001219 ncharsdeleted = 0
1220 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001221 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001222 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001223 chars = chars[:-1]
1224 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001225 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001226 if have <= want or chars[-1] not in " \t":
1227 break
1228 text.undo_block_start()
1229 text.delete("insert-%dc" % ncharsdeleted, "insert")
1230 if have < want:
1231 text.insert("insert", ' ' * (want - have))
1232 text.undo_block_stop()
1233 return "break"
1234
1235 def smart_indent_event(self, event):
1236 # if intraline selection:
1237 # delete it
1238 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001239 # do indent-region
1240 # else:
1241 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001242 text = self.text
1243 first, last = self.get_selection_indices()
1244 text.undo_block_start()
1245 try:
1246 if first and last:
1247 if index2line(first) != index2line(last):
1248 return self.indent_region_event(event)
1249 text.delete(first, last)
1250 text.mark_set("insert", first)
1251 prefix = text.get("insert linestart", "insert")
1252 raw, effective = classifyws(prefix, self.tabwidth)
1253 if raw == len(prefix):
1254 # only whitespace to the left
1255 self.reindent_to(effective + self.indentwidth)
1256 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001257 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001258 if self.usetabs:
1259 pad = '\t'
1260 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001261 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001262 n = self.indentwidth
1263 pad = ' ' * (n - effective % n)
1264 text.insert("insert", pad)
1265 text.see("insert")
1266 return "break"
1267 finally:
1268 text.undo_block_stop()
1269
1270 def newline_and_indent_event(self, event):
1271 text = self.text
1272 first, last = self.get_selection_indices()
1273 text.undo_block_start()
1274 try:
1275 if first and last:
1276 text.delete(first, last)
1277 text.mark_set("insert", first)
1278 line = text.get("insert linestart", "insert")
1279 i, n = 0, len(line)
1280 while i < n and line[i] in " \t":
1281 i = i+1
1282 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001283 # the cursor is in or at leading indentation in a continuation
1284 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001285 text.insert("insert linestart", '\n')
1286 return "break"
1287 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001288 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001289 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001290 last_line_of_prompt = sys.ps1.split('\n')[-1]
1291 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001292 line = line[:-1]
1293 i = i+1
1294 if i:
1295 text.delete("insert - %d chars" % i, "insert")
1296 # strip whitespace after insert point
1297 while text.get("insert") in " \t":
1298 text.delete("insert")
1299 # start new line
1300 text.insert("insert", '\n')
1301
1302 # adjust indentation for continuations and block
1303 # open/close first need to find the last stmt
1304 lno = index2line(text.index('insert'))
1305 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001306 if not self.context_use_ps1:
1307 for context in self.num_context_lines:
1308 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001309 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001310 rawtext = text.get(startatindex, "insert")
1311 y.set_str(rawtext)
1312 bod = y.find_good_parse_start(
1313 self.context_use_ps1,
1314 self._build_char_in_string_func(startatindex))
1315 if bod is not None or startat == 1:
1316 break
1317 y.set_lo(bod or 0)
1318 else:
1319 r = text.tag_prevrange("console", "insert")
1320 if r:
1321 startatindex = r[1]
1322 else:
1323 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001324 rawtext = text.get(startatindex, "insert")
1325 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001326 y.set_lo(0)
1327
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001328 c = y.get_continuation_type()
1329 if c != PyParse.C_NONE:
1330 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001331 if c == PyParse.C_STRING_FIRST_LINE:
1332 # after the first line of a string; do not indent at all
1333 pass
1334 elif c == PyParse.C_STRING_NEXT_LINES:
1335 # inside a string which started before this line;
1336 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001337 text.insert("insert", indent)
1338 elif c == PyParse.C_BRACKET:
1339 # line up with the first (if any) element of the
1340 # last open bracket structure; else indent one
1341 # level beyond the indent of the line with the
1342 # last open bracket
1343 self.reindent_to(y.compute_bracket_indent())
1344 elif c == PyParse.C_BACKSLASH:
1345 # if more than one line in this stmt already, just
1346 # mimic the current indent; else if initial line
1347 # has a start on an assignment stmt, indent to
1348 # beyond leftmost =; else to beyond first chunk of
1349 # non-whitespace on initial line
1350 if y.get_num_lines_in_stmt() > 1:
1351 text.insert("insert", indent)
1352 else:
1353 self.reindent_to(y.compute_backslash_indent())
1354 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001355 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001356 return "break"
1357
1358 # This line starts a brand new stmt; indent relative to
1359 # indentation of initial line of closest preceding
1360 # interesting stmt.
1361 indent = y.get_base_indent_string()
1362 text.insert("insert", indent)
1363 if y.is_block_opener():
1364 self.smart_indent_event(event)
1365 elif indent and y.is_block_closer():
1366 self.smart_backspace_event(event)
1367 return "break"
1368 finally:
1369 text.see("insert")
1370 text.undo_block_stop()
1371
Martin Panter7462b6492015-11-02 03:37:02 +00001372 # Our editwin provides an is_char_in_string function that works
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001373 # with a Tk text index, but PyParse only knows about offsets into
1374 # a string. This builds a function for PyParse that accepts an
1375 # offset.
1376
1377 def _build_char_in_string_func(self, startindex):
1378 def inner(offset, _startindex=startindex,
1379 _icis=self.is_char_in_string):
1380 return _icis(_startindex + "+%dc" % offset)
1381 return inner
1382
1383 def indent_region_event(self, event):
1384 head, tail, chars, lines = self.get_region()
1385 for pos in range(len(lines)):
1386 line = lines[pos]
1387 if line:
1388 raw, effective = classifyws(line, self.tabwidth)
1389 effective = effective + self.indentwidth
1390 lines[pos] = self._make_blanks(effective) + line[raw:]
1391 self.set_region(head, tail, chars, lines)
1392 return "break"
1393
1394 def dedent_region_event(self, event):
1395 head, tail, chars, lines = self.get_region()
1396 for pos in range(len(lines)):
1397 line = lines[pos]
1398 if line:
1399 raw, effective = classifyws(line, self.tabwidth)
1400 effective = max(effective - self.indentwidth, 0)
1401 lines[pos] = self._make_blanks(effective) + line[raw:]
1402 self.set_region(head, tail, chars, lines)
1403 return "break"
1404
1405 def comment_region_event(self, event):
1406 head, tail, chars, lines = self.get_region()
1407 for pos in range(len(lines) - 1):
1408 line = lines[pos]
1409 lines[pos] = '##' + line
1410 self.set_region(head, tail, chars, lines)
1411
1412 def uncomment_region_event(self, event):
1413 head, tail, chars, lines = self.get_region()
1414 for pos in range(len(lines)):
1415 line = lines[pos]
1416 if not line:
1417 continue
1418 if line[:2] == '##':
1419 line = line[2:]
1420 elif line[:1] == '#':
1421 line = line[1:]
1422 lines[pos] = line
1423 self.set_region(head, tail, chars, lines)
1424
1425 def tabify_region_event(self, event):
1426 head, tail, chars, lines = self.get_region()
1427 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001428 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001429 for pos in range(len(lines)):
1430 line = lines[pos]
1431 if line:
1432 raw, effective = classifyws(line, tabwidth)
1433 ntabs, nspaces = divmod(effective, tabwidth)
1434 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1435 self.set_region(head, tail, chars, lines)
1436
1437 def untabify_region_event(self, event):
1438 head, tail, chars, lines = self.get_region()
1439 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001440 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001441 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001442 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001443 self.set_region(head, tail, chars, lines)
1444
1445 def toggle_tabs_event(self, event):
1446 if self.askyesno(
1447 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001448 "Turn tabs " + ("on", "off")[self.usetabs] +
1449 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001450 ("will be", "remains at")[self.usetabs] + " 8." +
1451 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001452 parent=self.text):
1453 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001454 # Try to prevent inconsistent indentation.
1455 # User must change indent width manually after using tabs.
1456 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001457 return "break"
1458
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001459 # XXX this isn't bound to anything -- see tabwidth comments
1460## def change_tabwidth_event(self, event):
1461## new = self._asktabwidth()
1462## if new != self.tabwidth:
1463## self.tabwidth = new
1464## self.set_indentation_params(0, guess=0)
1465## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001466
1467 def change_indentwidth_event(self, event):
1468 new = self.askinteger(
1469 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001470 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001471 parent=self.text,
1472 initialvalue=self.indentwidth,
1473 minvalue=2,
1474 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001475 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001476 self.indentwidth = new
1477 return "break"
1478
1479 def get_region(self):
1480 text = self.text
1481 first, last = self.get_selection_indices()
1482 if first and last:
1483 head = text.index(first + " linestart")
1484 tail = text.index(last + "-1c lineend +1c")
1485 else:
1486 head = text.index("insert linestart")
1487 tail = text.index("insert lineend +1c")
1488 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001489 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001490 return head, tail, chars, lines
1491
1492 def set_region(self, head, tail, chars, lines):
1493 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001494 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001495 if newchars == chars:
1496 text.bell()
1497 return
1498 text.tag_remove("sel", "1.0", "end")
1499 text.mark_set("insert", head)
1500 text.undo_block_start()
1501 text.delete(head, tail)
1502 text.insert(head, newchars)
1503 text.undo_block_stop()
1504 text.tag_add("sel", head, "insert")
1505
1506 # Make string that displays as n leading blanks.
1507
1508 def _make_blanks(self, n):
1509 if self.usetabs:
1510 ntabs, nspaces = divmod(n, self.tabwidth)
1511 return '\t' * ntabs + ' ' * nspaces
1512 else:
1513 return ' ' * n
1514
1515 # Delete from beginning of line to insert point, then reinsert
1516 # column logical (meaning use tabs if appropriate) spaces.
1517
1518 def reindent_to(self, column):
1519 text = self.text
1520 text.undo_block_start()
1521 if text.compare("insert linestart", "!=", "insert"):
1522 text.delete("insert linestart", "insert")
1523 if column:
1524 text.insert("insert", self._make_blanks(column))
1525 text.undo_block_stop()
1526
1527 def _asktabwidth(self):
1528 return self.askinteger(
1529 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001530 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001531 parent=self.text,
1532 initialvalue=self.indentwidth,
1533 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001534 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001535
1536 # Guess indentwidth from text content.
1537 # Return guessed indentwidth. This should not be believed unless
1538 # it's in a reasonable range (e.g., it will be 0 if no indented
1539 # blocks are found).
1540
1541 def guess_indent(self):
1542 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1543 if opener and indented:
1544 raw, indentsmall = classifyws(opener, self.tabwidth)
1545 raw, indentlarge = classifyws(indented, self.tabwidth)
1546 else:
1547 indentsmall = indentlarge = 0
1548 return indentlarge - indentsmall
1549
1550# "line.col" -> line, as an int
1551def index2line(index):
1552 return int(float(index))
1553
1554# Look at the leading whitespace in s.
1555# Return pair (# of leading ws characters,
1556# effective # of leading blanks after expanding
1557# tabs to width tabwidth)
1558
1559def classifyws(s, tabwidth):
1560 raw = effective = 0
1561 for ch in s:
1562 if ch == ' ':
1563 raw = raw + 1
1564 effective = effective + 1
1565 elif ch == '\t':
1566 raw = raw + 1
1567 effective = (effective // tabwidth + 1) * tabwidth
1568 else:
1569 break
1570 return raw, effective
1571
1572import tokenize
1573_tokenize = tokenize
1574del tokenize
1575
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001576class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001577
1578 # .run() chews over the Text widget, looking for a block opener
1579 # and the stmt following it. Returns a pair,
1580 # (line containing block opener, line containing stmt)
1581 # Either or both may be None.
1582
1583 def __init__(self, text, tabwidth):
1584 self.text = text
1585 self.tabwidth = tabwidth
1586 self.i = self.finished = 0
1587 self.blkopenline = self.indentedline = None
1588
1589 def readline(self):
1590 if self.finished:
1591 return ""
1592 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001593 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001594 if self.text.compare(mark, ">=", "end"):
1595 return ""
1596 return self.text.get(mark, mark + " lineend+1c")
1597
1598 def tokeneater(self, type, token, start, end, line,
1599 INDENT=_tokenize.INDENT,
1600 NAME=_tokenize.NAME,
1601 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1602 if self.finished:
1603 pass
1604 elif type == NAME and token in OPENERS:
1605 self.blkopenline = line
1606 elif type == INDENT and self.blkopenline:
1607 self.indentedline = line
1608 self.finished = 1
1609
1610 def run(self):
1611 save_tabsize = _tokenize.tabsize
1612 _tokenize.tabsize = self.tabwidth
1613 try:
1614 try:
Trent Nelson428de652008-03-18 22:41:35 +00001615 tokens = _tokenize.generate_tokens(self.readline)
1616 for token in tokens:
1617 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001618 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001619 # since we cut off the tokenizer early, we can trigger
1620 # spurious errors
1621 pass
1622 finally:
1623 _tokenize.tabsize = save_tabsize
1624 return self.blkopenline, self.indentedline
1625
1626### end autoindent code ###
1627
David Scherer7aced172000-08-15 01:13:23 +00001628def prepstr(s):
1629 # Helper to extract the underscore from a string, e.g.
1630 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001631 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001632 if i >= 0:
1633 s = s[:i] + s[i+1:]
1634 return i, s
1635
1636
1637keynames = {
1638 'bracketleft': '[',
1639 'bracketright': ']',
1640 'slash': '/',
1641}
1642
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001643def get_accelerator(keydefs, eventname):
1644 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001645 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1646 # if not keylist:
Ned Deilyb7601672014-03-27 20:49:14 -07001647 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001648 "<<open-module>>",
1649 "<<goto-line>>",
1650 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001651 return ""
1652 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001653 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001654 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1655 s = re.sub("Key-", "", s)
1656 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1657 s = re.sub("Control-", "Ctrl-", s)
1658 s = re.sub("-", "+", s)
1659 s = re.sub("><", " ", s)
1660 s = re.sub("<", "", s)
1661 s = re.sub(">", "", s)
1662 return s
1663
1664
1665def fixwordbreaks(root):
1666 # Make sure that Tk's double-click and next/previous word
1667 # operations use our definition of a word (i.e. an identifier)
1668 tk = root.tk
1669 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1670 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1671 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1672
1673
Terry Jan Reedycd567362014-10-17 01:31:35 -04001674def _editor_window(parent): # htest #
1675 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001676 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001677 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001678 if sys.argv[1:]:
1679 filename = sys.argv[1]
1680 else:
1681 filename = None
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001682 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001683 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001684 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001685 # Does not stop error, neither does following
1686 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001687
1688if __name__ == '__main__':
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001689 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001690 run(_editor_window)