blob: 5b4ed30323627e9208cb18d961d75dcb20ad42f4 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
Terry Jan Reedyc11633e2014-08-14 21:54:38 -04003import platform
David Scherer7aced172000-08-15 01:13:23 +00004import re
5import imp
Georg Brandl6634bf22008-05-20 07:13:37 +00006from Tkinter import *
7import tkSimpleDialog
8import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00009import webbrowser
Florent Xiclunad630c042010-04-02 07:24:52 +000010
11from idlelib.MultiCall import MultiCallCreator
Florent Xiclunad630c042010-04-02 07:24:52 +000012from idlelib import WindowList
13from idlelib import SearchDialog
14from idlelib import GrepDialog
15from idlelib import ReplaceDialog
16from idlelib import PyParse
17from idlelib.configHandler import idleConf
18from idlelib import aboutDialog, textView, configDialog
19from idlelib import macosxSupport
Terry Jan Reedy70e763c2015-09-20 19:55:44 -040020from idlelib import help
David Scherer7aced172000-08-15 01:13:23 +000021
22# The default tab setting for a Text widget, in average-width characters.
23TK_TABWIDTH_DEFAULT = 8
24
Terry Jan Reedyc11633e2014-08-14 21:54:38 -040025_py_version = ' (%s)' % platform.python_version()
26
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000027def _sphinx_version():
28 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
29 major, minor, micro, level, serial = sys.version_info
30 release = '%s%s' % (major, minor)
31 if micro:
Benjamin Petersonfb234632009-06-13 15:42:23 +000032 release += '%s' % (micro,)
33 if level == 'candidate':
34 release += 'rc%s' % (serial,)
35 elif level != 'final':
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000036 release += '%s%s' % (level[0], serial)
37 return release
38
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000039def _find_module(fullname, path=None):
40 """Version of imp.find_module() that handles hierarchical module names"""
41
42 file = None
43 for tgt in fullname.split('.'):
44 if file is not None:
45 file.close() # close intermediate files
46 (file, filename, descr) = imp.find_module(tgt, path)
47 if descr[2] == imp.PY_SOURCE:
48 break # find but not load the source file
49 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000050 try:
51 path = module.__path__
52 except AttributeError:
53 raise ImportError, 'No source for module ' + module.__name__
Raymond Hettinger179816d2011-04-12 18:54:46 -070054 if descr[2] != imp.PY_SOURCE:
55 # If all of the above fails and didn't raise an exception,fallback
56 # to a straight import which can find __init__.py in a package.
57 m = __import__(fullname)
58 try:
59 filename = m.__file__
60 except AttributeError:
61 pass
62 else:
63 file = None
64 base, ext = os.path.splitext(filename)
65 if ext == '.pyc':
66 ext = '.py'
67 filename = base + ext
68 descr = filename, None, imp.PY_SOURCE
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000069 return file, filename, descr
70
Terry Jan Reedyadb87e22012-02-05 15:10:55 -050071
72class HelpDialog(object):
73
74 def __init__(self):
75 self.parent = None # parent of help window
76 self.dlg = None # the help window iteself
77
78 def display(self, parent, near=None):
79 """ Display the help dialog.
80
81 parent - parent widget for the help window
82
83 near - a Toplevel widget (e.g. EditorWindow or PyShell)
84 to use as a reference for placing the help window
85 """
Terry Jan Reedyfea7fc12015-09-22 22:59:35 -040086 import warnings as w
87 w.warn("EditorWindow.HelpDialog is no longer used by Idle.\n"
88 "It will be removed in 3.6 or later.\n"
89 "It has been replaced by private help.HelpWindow\n",
90 DeprecationWarning, stacklevel=2)
Terry Jan Reedyadb87e22012-02-05 15:10:55 -050091 if self.dlg is None:
92 self.show_dialog(parent)
93 if near:
94 self.nearwindow(near)
95
96 def show_dialog(self, parent):
97 self.parent = parent
98 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
99 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
100 dlg.bind('<Destroy>', self.destroy, '+')
101
102 def nearwindow(self, near):
103 # Place the help dialog near the window specified by parent.
104 # Note - this may not reposition the window in Metacity
105 # if "/apps/metacity/general/disable_workarounds" is enabled
106 dlg = self.dlg
107 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
108 dlg.withdraw()
109 dlg.geometry("=+%d+%d" % geom)
110 dlg.deiconify()
111 dlg.lift()
112
113 def destroy(self, ev=None):
114 self.dlg = None
115 self.parent = None
116
Terry Jan Reedy8b7122c2015-09-20 23:05:21 -0400117helpDialog = HelpDialog() # singleton instance, no longer used
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500118
119
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000120class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +0000121 from idlelib.Percolator import Percolator
122 from idlelib.ColorDelegator import ColorDelegator
123 from idlelib.UndoDelegator import UndoDelegator
124 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
125 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +0000126 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +0000127 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000128
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000129 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000130
131 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000132 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +0000133 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000134 if sys.platform.count('linux'):
135 # look for html docs in a couple of standard places
136 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
137 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
138 dochome = '/var/www/html/python/index.html'
139 else:
140 basepath = '/usr/share/doc/' # standard location
141 dochome = os.path.join(basepath, pyver,
142 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000143 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +0000144 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +0000145 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000146 if os.path.isfile(chmfile):
147 dochome = chmfile
Ned Deily57847df2014-03-27 20:47:04 -0700148 elif sys.platform == 'darwin':
149 # documentation may be stored inside a python framework
Ronald Oussoren19302d92006-06-11 14:33:36 +0000150 dochome = os.path.join(sys.prefix,
151 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000152 dochome = os.path.normpath(dochome)
153 if os.path.isfile(dochome):
154 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +0000155 if sys.platform == 'darwin':
156 # Safari requires real file:-URLs
157 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000158 else:
Terry Jan Reedyb17c1e02014-09-19 22:54:09 -0400159 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
David Scherer7aced172000-08-15 01:13:23 +0000160 self.flist = flist
161 root = root or flist.root
162 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +0000163 try:
164 sys.ps1
165 except AttributeError:
166 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000167 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000168 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000169 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000170 self.tkinter_vars = flist.vars
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200171 #self.top.instance_dict makes flist.inversedict available to
172 #configDialog.py so it can access all EditorWindow instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000173 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000174 else:
175 self.tkinter_vars = {} # keys: Tkinter event names
176 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000177 self.top.instance_dict = {}
178 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000179 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000180 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000181 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200182 self.width = idleConf.GetOption('main','EditorWindow','width', type='int')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000183 text_options = {
184 'name': 'text',
185 'padx': 5,
186 'wrap': 'none',
187 'width': self.width,
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200188 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')}
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000189 if TkVersion >= 8.5:
190 # Starting with tk 8.5 we have to set the new tabstyle option
191 # to 'wordprocessor' to achieve the same display of tabs as in
192 # older tk versions.
193 text_options['tabstyle'] = 'wordprocessor'
194 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000195 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000196
197 self.createmenubar()
198 self.apply_bindings()
199
200 self.top.protocol("WM_DELETE_WINDOW", self.close)
201 self.top.bind("<<close-window>>", self.close_event)
Ned Deily57847df2014-03-27 20:47:04 -0700202 if macosxSupport.isAquaTk():
Ronald Oussoren17db4952006-07-23 09:41:09 +0000203 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000204 text.bind('<<close-window>>', self.close_event)
Terry Jan Reedyca33d562015-09-22 21:10:22 -0400205 # Some OS X systems have only one mouse button, so use
206 # control-click for popup context menus there. For two
207 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murray3f752ab2010-12-18 17:22:18 +0000208 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedyca33d562015-09-22 21:10:22 -0400209 text.bind("<2>", self.right_menu_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000210 else:
Terry Jan Reedyca33d562015-09-22 21:10:22 -0400211 # Elsewhere, use right-click for popup menus.
R. David Murray3f752ab2010-12-18 17:22:18 +0000212 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000213 text.bind("<<cut>>", self.cut)
214 text.bind("<<copy>>", self.copy)
215 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000216 text.bind("<<center-insert>>", self.center_insert_event)
217 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000218 text.bind("<<python-docs>>", self.python_docs)
219 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000220 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000221 text.bind("<<open-module>>", self.open_module)
222 text.bind("<<do-nothing>>", lambda event: "break")
223 text.bind("<<select-all>>", self.select_all)
224 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000225 text.bind("<<find>>", self.find_event)
226 text.bind("<<find-again>>", self.find_again_event)
227 text.bind("<<find-in-files>>", self.find_in_files_event)
228 text.bind("<<find-selection>>", self.find_selection_event)
229 text.bind("<<replace>>", self.replace_event)
230 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000231 text.bind("<<smart-backspace>>",self.smart_backspace_event)
232 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
233 text.bind("<<smart-indent>>",self.smart_indent_event)
234 text.bind("<<indent-region>>",self.indent_region_event)
235 text.bind("<<dedent-region>>",self.dedent_region_event)
236 text.bind("<<comment-region>>",self.comment_region_event)
237 text.bind("<<uncomment-region>>",self.uncomment_region_event)
238 text.bind("<<tabify-region>>",self.tabify_region_event)
239 text.bind("<<untabify-region>>",self.untabify_region_event)
240 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
241 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000242 text.bind("<Left>", self.move_at_edge_if_selection(0))
243 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000244 text.bind("<<del-word-left>>", self.del_word_left)
245 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000246 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000247
David Scherer7aced172000-08-15 01:13:23 +0000248 if flist:
249 flist.inversedict[self] = key
250 if key:
251 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000252 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000253 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
254 text.bind("<<open-class-browser>>", self.open_class_browser)
255 text.bind("<<open-path-browser>>", self.open_path_browser)
256
Steven M. Gava898a3652001-10-07 11:10:44 +0000257 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000258 vbar['command'] = text.yview
259 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000260 text['yscrollcommand'] = vbar.set
Terry Jan Reedy12352142015-08-01 18:57:27 -0400261 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000262 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
263 text.pack(side=TOP, fill=BOTH, expand=1)
264 text.focus_set()
265
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000266 # usetabs true -> literal tab characters are used by indent and
267 # dedent cmds, possibly mixed with spaces if
268 # indentwidth is not a multiple of tabwidth,
269 # which will cause Tabnanny to nag!
270 # false -> tab characters are converted to spaces by indent
271 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000272 # Although use-spaces=0 can be configured manually in config-main.def,
273 # configuration of tabs v. spaces is not supported in the configuration
274 # dialog. IDLE promotes the preferred Python indentation: use spaces!
275 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
276 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000277
278 # tabwidth is the display width of a literal tab character.
279 # CAUTION: telling Tk to use anything other than its default
280 # tab setting causes it to use an entirely different tabbing algorithm,
281 # treating tab stops as fixed distances from the left margin.
282 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000283 self.tabwidth = 8 # must remain 8 until Tk is fixed.
284
285 # indentwidth is the number of screen characters per indent level.
286 # The recommended Python indentation is four spaces.
287 self.indentwidth = self.tabwidth
288 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000289
290 # If context_use_ps1 is true, parsing searches back for a ps1 line;
291 # else searches for a popular (if, def, ...) Python stmt.
292 self.context_use_ps1 = False
293
294 # When searching backwards for a reliable place to begin parsing,
295 # first start num_context_lines[0] lines back, then
296 # num_context_lines[1] lines back if that didn't work, and so on.
297 # The last value should be huge (larger than the # of lines in a
298 # conceivable file).
299 # Making the initial values larger slows things down more often.
300 self.num_context_lines = 50, 500, 5000000
301
David Scherer7aced172000-08-15 01:13:23 +0000302 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000303
304 self.undo = undo = self.UndoDelegator()
305 per.insertfilter(undo)
306 text.undo_block_start = undo.undo_block_start
307 text.undo_block_stop = undo.undo_block_stop
308 undo.set_saved_change_hook(self.saved_change_hook)
309
310 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000311 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000312 io.set_filename_change_hook(self.filename_change_hook)
313
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000314 # Create the recent files submenu
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400315 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000316 self.menudict['file'].insert_cascade(3, label='Recent Files',
317 underline=0,
318 menu=self.recent_files_menu)
319 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000320
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000321 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000322 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000323 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000324 io.loadfile(filename)
325 else:
326 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000327 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000328 self.saved_change_hook()
329
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000330 self.set_indentation_params(self.ispythonsource(filename))
331
David Scherer7aced172000-08-15 01:13:23 +0000332 self.load_extensions()
333
334 menu = self.menudict.get('windows')
335 if menu:
336 end = menu.index("end")
337 if end is None:
338 end = -1
339 if end >= 0:
340 menu.add_separator()
341 end = end + 1
342 self.wmenu_end = end
343 WindowList.register_callback(self.postwindowsmenu)
344
345 # Some abstractions so IDLE extensions are cross-IDE
346 self.askyesno = tkMessageBox.askyesno
347 self.askinteger = tkSimpleDialog.askinteger
348 self.showerror = tkMessageBox.showerror
349
Martin v. Löwis307021f2005-11-27 16:59:04 +0000350 def _filename_to_unicode(self, filename):
351 """convert filename to unicode in order to display it in Tk"""
352 if isinstance(filename, unicode) or not filename:
353 return filename
354 else:
355 try:
356 return filename.decode(self.filesystemencoding)
357 except UnicodeDecodeError:
358 # XXX
359 try:
360 return filename.decode(self.encoding)
361 except UnicodeDecodeError:
362 # byte-to-byte conversion
363 return filename.decode('iso8859-1')
364
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000365 def new_callback(self, event):
366 dirname, basename = self.io.defaultfilename()
367 self.flist.new(dirname)
368 return "break"
369
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000370 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400371 if (event.state & 4) != 0 and event.keysym == "Home":
372 # state&4==Control. If <Control-Home>, use the Tk binding.
373 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000374 if self.text.index("iomark") and \
375 self.text.compare("iomark", "<=", "insert lineend") and \
376 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400377 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000378 insertpt = int(self.text.index("iomark").split(".")[1])
379 else:
380 line = self.text.get("insert linestart", "insert lineend")
381 for insertpt in xrange(len(line)):
382 if line[insertpt] not in (' ','\t'):
383 break
384 else:
385 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000386 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000387 if insertpt == lineat:
388 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000389 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000390 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400391 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000392 self.text.tag_remove("sel", "1.0", "end")
393 else:
394 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400395 self.text.mark_set("my_anchor", "insert") # there was no previous selection
396 else:
397 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
398 self.text.mark_set("my_anchor", "sel.first") # extend back
399 else:
400 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000401 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400402 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000403 if self.text.compare(first,">",last):
404 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000405 self.text.tag_remove("sel", "1.0", "end")
406 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000407 self.text.mark_set("insert", dest)
408 self.text.see("insert")
409 return "break"
410
David Scherer7aced172000-08-15 01:13:23 +0000411 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000412 self.status_bar = self.MultiStatusBar(self.top)
Ned Deily57847df2014-03-27 20:47:04 -0700413 if sys.platform == "darwin":
Ronald Oussoren19302d92006-06-11 14:33:36 +0000414 # Insert some padding to avoid obscuring some of the statusbar
415 # by the resize widget.
416 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000417 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
418 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
419 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000420 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
421 self.text.event_add("<<set-line-and-column>>",
422 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000423 self.text.after_idle(self.set_line_and_column)
424
425 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000426 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000427 self.status_bar.set_label('column', 'Col: %s' % column)
428 self.status_bar.set_label('line', 'Ln: %s' % line)
429
David Scherer7aced172000-08-15 01:13:23 +0000430 menu_specs = [
431 ("file", "_File"),
432 ("edit", "_Edit"),
433 ("format", "F_ormat"),
434 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000435 ("options", "_Options"),
Ned Deilyb5daa3d2015-01-17 21:03:41 -0800436 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000437 ("help", "_Help"),
438 ]
439
Ronald Oussoren19302d92006-06-11 14:33:36 +0000440
David Scherer7aced172000-08-15 01:13:23 +0000441 def createmenubar(self):
442 mbar = self.menubar
443 self.menudict = menudict = {}
444 for name, label in self.menu_specs:
445 underline, label = prepstr(label)
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400446 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000447 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000448
Ned Deily57847df2014-03-27 20:47:04 -0700449 if macosxSupport.isCarbonTk():
Ronald Oussoren19302d92006-06-11 14:33:36 +0000450 # Insert the application menu
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400451 menudict['application'] = menu = Menu(mbar, name='apple',
452 tearoff=0)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000453 mbar.add_cascade(label='IDLE', menu=menu)
454
David Scherer7aced172000-08-15 01:13:23 +0000455 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000456 self.base_helpmenu_length = self.menudict['help'].index(END)
457 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000458
459 def postwindowsmenu(self):
460 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000461 menu = self.menudict['windows']
462 end = menu.index("end")
463 if end is None:
464 end = -1
465 if end > self.wmenu_end:
466 menu.delete(self.wmenu_end+1, end)
467 WindowList.add_windows_to_menu(menu)
468
469 rmenu = None
470
471 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000472 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
473 if not self.rmenu:
474 self.make_rmenu()
475 rmenu = self.rmenu
476 self.event = event
477 iswin = sys.platform[:3] == 'win'
478 if iswin:
479 self.text.config(cursor="arrow")
Andrew Svetlov5018db72012-11-01 22:39:14 +0200480
Roger Serwy231a8fd2013-04-07 12:15:52 -0500481 for item in self.rmenu_specs:
482 try:
483 label, eventname, verify_state = item
484 except ValueError: # see issue1207589
485 continue
486
Andrew Svetlov5018db72012-11-01 22:39:14 +0200487 if verify_state is None:
488 continue
489 state = getattr(self, verify_state)()
490 rmenu.entryconfigure(label, state=state)
491
David Scherer7aced172000-08-15 01:13:23 +0000492 rmenu.tk_popup(event.x_root, event.y_root)
493 if iswin:
494 self.text.config(cursor="ibeam")
495
496 rmenu_specs = [
Andrew Svetlov5018db72012-11-01 22:39:14 +0200497 # ("Label", "<<virtual-event>>", "statefuncname"), ...
498 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000499 ]
500
501 def make_rmenu(self):
502 rmenu = Menu(self.text, tearoff=0)
Roger Serwy231a8fd2013-04-07 12:15:52 -0500503 for item in self.rmenu_specs:
504 label, eventname = item[0], item[1]
Andrew Svetlov5018db72012-11-01 22:39:14 +0200505 if label is not None:
506 def command(text=self.text, eventname=eventname):
507 text.event_generate(eventname)
508 rmenu.add_command(label=label, command=command)
509 else:
510 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000511 self.rmenu = rmenu
512
Andrew Svetlov5018db72012-11-01 22:39:14 +0200513 def rmenu_check_cut(self):
514 return self.rmenu_check_copy()
515
516 def rmenu_check_copy(self):
517 try:
518 indx = self.text.index('sel.first')
519 except TclError:
520 return 'disabled'
521 else:
522 return 'normal' if indx else 'disabled'
523
524 def rmenu_check_paste(self):
525 try:
526 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
527 except TclError:
528 return 'disabled'
529 else:
530 return 'normal'
531
David Scherer7aced172000-08-15 01:13:23 +0000532 def about_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400533 "Handle Help 'About IDLE' event."
534 # Synchronize with macosxSupport.overrideRootMenu.about_dialog.
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000535 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000536
Steven M. Gava3b55a892001-11-21 05:56:26 +0000537 def config_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400538 "Handle Options 'Configure IDLE' event."
539 # Synchronize with macosxSupport.overrideRootMenu.config_dialog.
Steven M. Gava3b55a892001-11-21 05:56:26 +0000540 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400541
David Scherer7aced172000-08-15 01:13:23 +0000542 def help_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400543 "Handle Help 'IDLE Help' event."
544 # Synchronize with macosxSupport.overrideRootMenu.help_dialog.
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500545 if self.root:
546 parent = self.root
547 else:
548 parent = self.top
Terry Jan Reedy70e763c2015-09-20 19:55:44 -0400549 help.show_idlehelp(parent)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000550
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000551 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000552 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000553 try:
554 os.startfile(self.help_url)
555 except WindowsError as why:
556 tkMessageBox.showerror(title='Document Start Failure',
557 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000558 else:
559 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000560 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000561
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000562 def cut(self,event):
563 self.text.event_generate("<<Cut>>")
564 return "break"
565
566 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000567 if not self.text.tag_ranges("sel"):
568 # There is no selection, so do nothing and maybe interrupt.
569 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000570 self.text.event_generate("<<Copy>>")
571 return "break"
572
573 def paste(self,event):
574 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000575 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000576 return "break"
577
David Scherer7aced172000-08-15 01:13:23 +0000578 def select_all(self, event=None):
579 self.text.tag_add("sel", "1.0", "end-1c")
580 self.text.mark_set("insert", "1.0")
581 self.text.see("insert")
582 return "break"
583
584 def remove_selection(self, event=None):
585 self.text.tag_remove("sel", "1.0", "end")
586 self.text.see("insert")
587
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000588 def move_at_edge_if_selection(self, edge_index):
589 """Cursor move begins at start or end of selection
590
591 When a left/right cursor key is pressed create and return to Tkinter a
592 function which causes a cursor move from the associated edge of the
593 selection.
594
595 """
596 self_text_index = self.text.index
597 self_text_mark_set = self.text.mark_set
598 edges_table = ("sel.first+1c", "sel.last-1c")
599 def move_at_edge(event):
600 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
601 try:
602 self_text_index("sel.first")
603 self_text_mark_set("insert", edges_table[edge_index])
604 except TclError:
605 pass
606 return move_at_edge
607
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000608 def del_word_left(self, event):
609 self.text.event_generate('<Meta-Delete>')
610 return "break"
611
612 def del_word_right(self, event):
613 self.text.event_generate('<Meta-d>')
614 return "break"
615
Steven M. Gavac5976402002-01-04 03:06:08 +0000616 def find_event(self, event):
617 SearchDialog.find(self.text)
618 return "break"
619
620 def find_again_event(self, event):
621 SearchDialog.find_again(self.text)
622 return "break"
623
624 def find_selection_event(self, event):
625 SearchDialog.find_selection(self.text)
626 return "break"
627
628 def find_in_files_event(self, event):
629 GrepDialog.grep(self.text, self.io, self.flist)
630 return "break"
631
632 def replace_event(self, event):
633 ReplaceDialog.replace(self.text)
634 return "break"
635
636 def goto_line_event(self, event):
637 text = self.text
638 lineno = tkSimpleDialog.askinteger("Goto",
639 "Go to line number:",parent=text)
640 if lineno is None:
641 return "break"
642 if lineno <= 0:
643 text.bell()
644 return "break"
645 text.mark_set("insert", "%d.0" % lineno)
646 text.see("insert")
647
David Scherer7aced172000-08-15 01:13:23 +0000648 def open_module(self, event=None):
649 # XXX Shouldn't this be in IOBinding or in FileList?
650 try:
651 name = self.text.get("sel.first", "sel.last")
652 except TclError:
653 name = ""
654 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000655 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000656 name = tkSimpleDialog.askstring("Module",
657 "Enter the name of a Python module\n"
658 "to search on sys.path and open:",
659 parent=self.text, initialvalue=name)
660 if name:
661 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000662 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000663 return
David Scherer7aced172000-08-15 01:13:23 +0000664 # XXX Ought to insert current file's directory in front of path
665 try:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400666 (f, file_path, (suffix, mode, mtype)) = _find_module(name)
Terry Jan Reedy2b149862013-06-29 00:59:34 -0400667 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000668 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
669 return
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400670 if mtype != imp.PY_SOURCE:
David Scherer7aced172000-08-15 01:13:23 +0000671 tkMessageBox.showerror("Unsupported type",
672 "%s is not a source module" % name, parent=self.text)
673 return
674 if f:
675 f.close()
676 if self.flist:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400677 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000678 else:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400679 self.io.loadfile(file_path)
680 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000681
682 def open_class_browser(self, event=None):
683 filename = self.io.filename
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400684 if not (self.__class__.__name__ == 'PyShellEditorWindow'
685 and filename):
686 filename = self.open_module()
687 if filename is None:
688 return
David Scherer7aced172000-08-15 01:13:23 +0000689 head, tail = os.path.split(filename)
690 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000691 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000692 ClassBrowser.ClassBrowser(self.flist, base, [head])
693
694 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000695 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000696 PathBrowser.PathBrowser(self.flist)
697
698 def gotoline(self, lineno):
699 if lineno is not None and lineno > 0:
700 self.text.mark_set("insert", "%d.0" % lineno)
701 self.text.tag_remove("sel", "1.0", "end")
702 self.text.tag_add("sel", "insert", "insert +1l")
703 self.center()
704
705 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000706 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000707 return True
David Scherer7aced172000-08-15 01:13:23 +0000708 base, ext = os.path.splitext(os.path.basename(filename))
709 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000710 return True
David Scherer7aced172000-08-15 01:13:23 +0000711 try:
712 f = open(filename)
713 line = f.readline()
714 f.close()
715 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000716 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000717 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000718
719 def close_hook(self):
720 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000721 self.flist.unregister_maybe_terminate(self)
722 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000723
724 def set_close_hook(self, close_hook):
725 self.close_hook = close_hook
726
727 def filename_change_hook(self):
728 if self.flist:
729 self.flist.filename_changed_edit(self)
730 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000731 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000732 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000733
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000734 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000735 if self.color:
736 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000737 if self.ispythonsource(self.io.filename):
738 self.color = self.ColorDelegator()
739 # can add more colorizers here...
740 if self.color:
741 self.per.removefilter(self.undo)
742 self.per.insertfilter(self.color)
743 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000744
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000745 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000746 if not self.color:
747 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000748 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000749 self.per.removefilter(self.color)
750 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000751
Steven M. Gavab77d3432002-03-02 07:16:21 +0000752 def ResetColorizer(self):
Terry Jan Reedy13755382014-10-09 18:44:26 -0400753 "Update the color theme"
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000754 # Called from self.filename_change_hook and from configDialog.py
755 self._rmcolorizer()
756 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000757 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000758 normal_colors = idleConf.GetHighlight(theme, 'normal')
759 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
760 select_colors = idleConf.GetHighlight(theme, 'hilite')
761 self.text.config(
762 foreground=normal_colors['foreground'],
763 background=normal_colors['background'],
764 insertbackground=cursor_color,
765 selectforeground=select_colors['foreground'],
766 selectbackground=select_colors['background'],
767 )
Terry Jan Reedya6673802015-09-28 04:52:44 -0400768 if TkVersion >= 8.5:
769 self.text.config(
770 inactiveselectbackground=select_colors['background'])
David Scherer7aced172000-08-15 01:13:23 +0000771
Steven M. Gavab1585412002-03-12 00:21:56 +0000772 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000773 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000774 # Called from configDialog.py
Terry Jan Reedy12352142015-08-01 18:57:27 -0400775
776 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000777
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000778 def RemoveKeybindings(self):
779 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000780 # 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 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000783 self.text.event_delete(event, *keylist)
784 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000785 xkeydefs = idleConf.GetExtensionBindings(extensionName)
786 if xkeydefs:
787 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000788 self.text.event_delete(event, *keylist)
789
790 def ApplyKeybindings(self):
791 "Update the keybindings after they are changed"
792 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000793 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000794 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000795 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000796 xkeydefs = idleConf.GetExtensionBindings(extensionName)
797 if xkeydefs:
798 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000799 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000800 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000801 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000802 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000803 for item in menu[1]:
804 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000805 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000806 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000807 menu = self.menudict[menubarItem]
Ned Deily14ef0c82013-07-20 14:38:24 -0700808 end = menu.index(END)
809 if end is None:
810 # Skip empty menus
811 continue
812 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000813 for index in range(0, end):
814 if menu.type(index) == 'command':
815 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000816 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000817 itemName = menu.entrycget(index, 'label')
818 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000819 if menubarItem in menuEventDict:
820 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000821 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000822 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000823 accel = get_accelerator(keydefs, event)
824 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000825
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000826 def set_notabs_indentwidth(self):
827 "Update the indentwidth if changed and not using tabs in this window"
828 # Called from configDialog.py
829 if not self.usetabs:
830 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
831 type='int')
832
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000833 def reset_help_menu_entries(self):
834 "Update the additional help entries on the Help menu"
835 help_list = idleConf.GetAllExtraHelpSourcesList()
836 helpmenu = self.menudict['help']
837 # first delete the extra help entries, if any
838 helpmenu_length = helpmenu.index(END)
839 if helpmenu_length > self.base_helpmenu_length:
840 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
841 # then rebuild them
842 if help_list:
843 helpmenu.add_separator()
844 for entry in help_list:
845 cmd = self.__extra_help_callback(entry[1])
846 helpmenu.add_command(label=entry[0], command=cmd)
847 # and update the menu dictionary
848 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000849
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000850 def __extra_help_callback(self, helpfile):
851 "Create a callback with the helpfile value frozen at definition time"
852 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000853 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000854 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000855 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000856 try:
857 os.startfile(helpfile)
858 except WindowsError as why:
859 tkMessageBox.showerror(title='Document Start Failure',
860 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000861 else:
862 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000863 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000864
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000865 def update_recent_files_list(self, new_file=None):
866 "Load and update the recent files list and menus"
867 rf_list = []
868 if os.path.exists(self.recent_files_path):
Terry Jan Reedyf9489432013-08-04 15:39:56 -0400869 with open(self.recent_files_path, 'r') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000870 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000871 if new_file:
872 new_file = os.path.abspath(new_file) + '\n'
873 if new_file in rf_list:
874 rf_list.remove(new_file) # move to top
875 rf_list.insert(0, new_file)
876 # clean and save the recent files list
877 bad_paths = []
878 for path in rf_list:
879 if '\0' in path or not os.path.exists(path[0:-1]):
880 bad_paths.append(path)
881 rf_list = [path for path in rf_list if path not in bad_paths]
882 ulchars = "1234567890ABCDEFGHIJK"
883 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000884 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800885 with open(self.recent_files_path, 'w') as rf_file:
886 rf_file.writelines(rf_list)
887 except IOError as err:
888 if not getattr(self.root, "recentfilelist_error_displayed", False):
889 self.root.recentfilelist_error_displayed = True
Terry Jan Reedy80487762015-10-27 03:37:55 -0400890 tkMessageBox.showwarning(title='IDLE Warning',
891 message="Cannot update File menu Recent Files list. "
892 "Your operating system says:\n%s\n"
893 "Select OK and IDLE will continue without updating."
Ned Deily40ad0412011-12-14 14:57:43 -0800894 % str(err),
895 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000896 # for each edit window instance, construct the recent files menu
897 for instance in self.top.instance_dict.keys():
898 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700899 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000900 for i, file_name in enumerate(rf_list):
901 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000902 # make unicode string to display non-ASCII chars correctly
903 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000904 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000905 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000906 command=callback,
907 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000908
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000909 def __recent_file_callback(self, file_name):
910 def open_recent_file(fn_closure=file_name):
911 self.io.open(editFile=fn_closure)
912 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000913
David Scherer7aced172000-08-15 01:13:23 +0000914 def saved_change_hook(self):
915 short = self.short_title()
916 long = self.long_title()
917 if short and long:
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400918 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000919 elif short:
920 title = short
921 elif long:
922 title = long
923 else:
924 title = "Untitled"
925 icon = short or long or title
926 if not self.get_saved():
927 title = "*%s*" % title
928 icon = "*%s" % icon
929 self.top.wm_title(title)
930 self.top.wm_iconname(icon)
931
932 def get_saved(self):
933 return self.undo.get_saved()
934
935 def set_saved(self, flag):
936 self.undo.set_saved(flag)
937
938 def reset_undo(self):
939 self.undo.reset_undo()
940
941 def short_title(self):
942 filename = self.io.filename
943 if filename:
944 filename = os.path.basename(filename)
Terry Jan Reedy59243652014-01-23 00:36:37 -0500945 else:
946 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000947 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400948 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000949
950 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000951 # return unicode string to display non-ASCII chars correctly
952 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000953
954 def center_insert_event(self, event):
955 self.center()
956
957 def center(self, mark="insert"):
958 text = self.text
959 top, bot = self.getwindowlines()
960 lineno = self.getlineno(mark)
961 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000962 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000963 text.yview(float(newtop))
964
965 def getwindowlines(self):
966 text = self.text
967 top = self.getlineno("@0,0")
968 bot = self.getlineno("@0,65535")
969 if top == bot and text.winfo_height() == 1:
970 # Geometry manager hasn't run yet
971 height = int(text['height'])
972 bot = top + height - 1
973 return top, bot
974
975 def getlineno(self, mark="insert"):
976 text = self.text
977 return int(float(text.index(mark)))
978
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000979 def get_geometry(self):
980 "Return (width, height, x, y)"
981 geom = self.top.wm_geometry()
982 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
983 tuple = (map(int, m.groups()))
984 return tuple
985
David Scherer7aced172000-08-15 01:13:23 +0000986 def close_event(self, event):
987 self.close()
988
989 def maybesave(self):
990 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000991 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000992 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000993 self.top.deiconify()
994 self.top.lower()
995 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000996 return self.io.maybesave()
997
998 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000999 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +00001000 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001001 self._close()
1002 return reply
1003
1004 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001005 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001006 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001007 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001008 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001009 self.io.close()
1010 self.io = None
1011 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001012 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001013 self.color.close(False)
1014 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001015 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001016 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001017 self.per.close()
1018 self.per = None
1019 self.top.destroy()
1020 if self.close_hook:
1021 # unless override: unregister from flist, terminate if last window
1022 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001023
1024 def load_extensions(self):
1025 self.extensions = {}
1026 self.load_standard_extensions()
1027
1028 def unload_extensions(self):
1029 for ins in self.extensions.values():
1030 if hasattr(ins, "close"):
1031 ins.close()
1032 self.extensions = {}
1033
1034 def load_standard_extensions(self):
1035 for name in self.get_standard_extension_names():
1036 try:
1037 self.load_extension(name)
1038 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001039 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001040 import traceback
1041 traceback.print_exc()
1042
1043 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001044 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001045
1046 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001047 try:
1048 mod = __import__(name, globals(), locals(), [])
1049 except ImportError:
1050 print "\nFailed to import extension: ", name
1051 return
David Scherer7aced172000-08-15 01:13:23 +00001052 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001053 keydefs = idleConf.GetExtensionBindings(name)
1054 if hasattr(cls, "menudefs"):
1055 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001056 ins = cls(self)
1057 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001058 if keydefs:
1059 self.apply_bindings(keydefs)
1060 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001061 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001062 while methodname[:1] == '<':
1063 methodname = methodname[1:]
1064 while methodname[-1:] == '>':
1065 methodname = methodname[:-1]
1066 methodname = methodname + "_event"
1067 if hasattr(ins, methodname):
1068 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001069
1070 def apply_bindings(self, keydefs=None):
1071 if keydefs is None:
1072 keydefs = self.Bindings.default_keydefs
1073 text = self.text
1074 text.keydefs = keydefs
1075 for event, keylist in keydefs.items():
1076 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001077 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001078
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001079 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001080 """Add appropriate entries to the menus and submenus
1081
1082 Menus that are absent or None in self.menudict are ignored.
1083 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001084 if menudefs is None:
1085 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001086 if keydefs is None:
1087 keydefs = self.Bindings.default_keydefs
1088 menudict = self.menudict
1089 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001090 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001091 menu = menudict.get(mname)
1092 if not menu:
1093 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001094 for entry in entrylist:
1095 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001096 menu.add_separator()
1097 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001098 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001099 checkbutton = (label[:1] == '!')
1100 if checkbutton:
1101 label = label[1:]
1102 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001103 accelerator = get_accelerator(keydefs, eventname)
1104 def command(text=text, eventname=eventname):
1105 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001106 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001107 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001108 menu.add_checkbutton(label=label, underline=underline,
1109 command=command, accelerator=accelerator,
1110 variable=var)
1111 else:
1112 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001113 command=command,
1114 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001115
1116 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001117 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001118 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001119 value = var.get()
1120 return value
1121 else:
1122 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001123
1124 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001125 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001126 if var:
1127 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001128 else:
1129 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001130
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001131 def get_var_obj(self, name, vartype=None):
1132 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001133 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001134 # create a Tkinter variable object with self.text as master:
1135 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001136 return var
1137
1138 # Tk implementations of "virtual text methods" -- each platform
1139 # reusing IDLE's support code needs to define these for its GUI's
1140 # flavor of widget.
1141
1142 # Is character at text_index in a Python string? Return 0 for
1143 # "guaranteed no", true for anything else. This info is expensive
1144 # to compute ab initio, but is probably already known by the
1145 # platform's colorizer.
1146
1147 def is_char_in_string(self, text_index):
1148 if self.color:
1149 # Return true iff colorizer hasn't (re)gotten this far
1150 # yet, or the character is tagged as being in a string
1151 return self.text.tag_prevrange("TODO", text_index) or \
1152 "STRING" in self.text.tag_names(text_index)
1153 else:
1154 # The colorizer is missing: assume the worst
1155 return 1
1156
1157 # If a selection is defined in the text widget, return (start,
1158 # end) as Tkinter text indices, otherwise return (None, None)
1159 def get_selection_indices(self):
1160 try:
1161 first = self.text.index("sel.first")
1162 last = self.text.index("sel.last")
1163 return first, last
1164 except TclError:
1165 return None, None
1166
1167 # Return the text widget's current view of what a tab stop means
1168 # (equivalent width in spaces).
1169
1170 def get_tabwidth(self):
1171 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1172 return int(current)
1173
1174 # Set the text widget's current view of what a tab stop means.
1175
1176 def set_tabwidth(self, newtabwidth):
1177 text = self.text
1178 if self.get_tabwidth() != newtabwidth:
1179 pixels = text.tk.call("font", "measure", text["font"],
1180 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001181 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001182 text.configure(tabs=pixels)
1183
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001184 # If ispythonsource and guess are true, guess a good value for
1185 # indentwidth based on file content (if possible), and if
1186 # indentwidth != tabwidth set usetabs false.
1187 # In any case, adjust the Text widget's view of what a tab
1188 # character means.
1189
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001190 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001191 if guess and ispythonsource:
1192 i = self.guess_indent()
1193 if 2 <= i <= 8:
1194 self.indentwidth = i
1195 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001196 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001197 self.set_tabwidth(self.tabwidth)
1198
1199 def smart_backspace_event(self, event):
1200 text = self.text
1201 first, last = self.get_selection_indices()
1202 if first and last:
1203 text.delete(first, last)
1204 text.mark_set("insert", first)
1205 return "break"
1206 # Delete whitespace left, until hitting a real char or closest
1207 # preceding virtual tab stop.
1208 chars = text.get("insert linestart", "insert")
1209 if chars == '':
1210 if text.compare("insert", ">", "1.0"):
1211 # easy: delete preceding newline
1212 text.delete("insert-1c")
1213 else:
1214 text.bell() # at start of buffer
1215 return "break"
1216 if chars[-1] not in " \t":
1217 # easy: delete preceding real char
1218 text.delete("insert-1c")
1219 return "break"
1220 # Ick. It may require *inserting* spaces if we back up over a
1221 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001222 tabwidth = self.tabwidth
1223 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001224 assert have > 0
1225 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001226 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001227 if self.context_use_ps1:
1228 last_line_of_prompt = sys.ps1.split('\n')[-1]
1229 else:
1230 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001231 ncharsdeleted = 0
1232 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001233 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001234 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001235 chars = chars[:-1]
1236 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001237 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001238 if have <= want or chars[-1] not in " \t":
1239 break
1240 text.undo_block_start()
1241 text.delete("insert-%dc" % ncharsdeleted, "insert")
1242 if have < want:
1243 text.insert("insert", ' ' * (want - have))
1244 text.undo_block_stop()
1245 return "break"
1246
1247 def smart_indent_event(self, event):
1248 # if intraline selection:
1249 # delete it
1250 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001251 # do indent-region
1252 # else:
1253 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001254 text = self.text
1255 first, last = self.get_selection_indices()
1256 text.undo_block_start()
1257 try:
1258 if first and last:
1259 if index2line(first) != index2line(last):
1260 return self.indent_region_event(event)
1261 text.delete(first, last)
1262 text.mark_set("insert", first)
1263 prefix = text.get("insert linestart", "insert")
1264 raw, effective = classifyws(prefix, self.tabwidth)
1265 if raw == len(prefix):
1266 # only whitespace to the left
1267 self.reindent_to(effective + self.indentwidth)
1268 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001269 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001270 if self.usetabs:
1271 pad = '\t'
1272 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001273 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001274 n = self.indentwidth
1275 pad = ' ' * (n - effective % n)
1276 text.insert("insert", pad)
1277 text.see("insert")
1278 return "break"
1279 finally:
1280 text.undo_block_stop()
1281
1282 def newline_and_indent_event(self, event):
1283 text = self.text
1284 first, last = self.get_selection_indices()
1285 text.undo_block_start()
1286 try:
1287 if first and last:
1288 text.delete(first, last)
1289 text.mark_set("insert", first)
1290 line = text.get("insert linestart", "insert")
1291 i, n = 0, len(line)
1292 while i < n and line[i] in " \t":
1293 i = i+1
1294 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001295 # the cursor is in or at leading indentation in a continuation
1296 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001297 text.insert("insert linestart", '\n')
1298 return "break"
1299 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001300 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001301 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001302 last_line_of_prompt = sys.ps1.split('\n')[-1]
1303 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001304 line = line[:-1]
1305 i = i+1
1306 if i:
1307 text.delete("insert - %d chars" % i, "insert")
1308 # strip whitespace after insert point
1309 while text.get("insert") in " \t":
1310 text.delete("insert")
1311 # start new line
1312 text.insert("insert", '\n')
1313
1314 # adjust indentation for continuations and block
1315 # open/close first need to find the last stmt
1316 lno = index2line(text.index('insert'))
1317 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001318 if not self.context_use_ps1:
1319 for context in self.num_context_lines:
1320 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001321 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001322 rawtext = text.get(startatindex, "insert")
1323 y.set_str(rawtext)
1324 bod = y.find_good_parse_start(
1325 self.context_use_ps1,
1326 self._build_char_in_string_func(startatindex))
1327 if bod is not None or startat == 1:
1328 break
1329 y.set_lo(bod or 0)
1330 else:
1331 r = text.tag_prevrange("console", "insert")
1332 if r:
1333 startatindex = r[1]
1334 else:
1335 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001336 rawtext = text.get(startatindex, "insert")
1337 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001338 y.set_lo(0)
1339
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001340 c = y.get_continuation_type()
1341 if c != PyParse.C_NONE:
1342 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001343 if c == PyParse.C_STRING_FIRST_LINE:
1344 # after the first line of a string; do not indent at all
1345 pass
1346 elif c == PyParse.C_STRING_NEXT_LINES:
1347 # inside a string which started before this line;
1348 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001349 text.insert("insert", indent)
1350 elif c == PyParse.C_BRACKET:
1351 # line up with the first (if any) element of the
1352 # last open bracket structure; else indent one
1353 # level beyond the indent of the line with the
1354 # last open bracket
1355 self.reindent_to(y.compute_bracket_indent())
1356 elif c == PyParse.C_BACKSLASH:
1357 # if more than one line in this stmt already, just
1358 # mimic the current indent; else if initial line
1359 # has a start on an assignment stmt, indent to
1360 # beyond leftmost =; else to beyond first chunk of
1361 # non-whitespace on initial line
1362 if y.get_num_lines_in_stmt() > 1:
1363 text.insert("insert", indent)
1364 else:
1365 self.reindent_to(y.compute_backslash_indent())
1366 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001367 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001368 return "break"
1369
1370 # This line starts a brand new stmt; indent relative to
1371 # indentation of initial line of closest preceding
1372 # interesting stmt.
1373 indent = y.get_base_indent_string()
1374 text.insert("insert", indent)
1375 if y.is_block_opener():
1376 self.smart_indent_event(event)
1377 elif indent and y.is_block_closer():
1378 self.smart_backspace_event(event)
1379 return "break"
1380 finally:
1381 text.see("insert")
1382 text.undo_block_stop()
1383
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001384 # Our editwin provides a is_char_in_string function that works
1385 # with a Tk text index, but PyParse only knows about offsets into
1386 # a string. This builds a function for PyParse that accepts an
1387 # offset.
1388
1389 def _build_char_in_string_func(self, startindex):
1390 def inner(offset, _startindex=startindex,
1391 _icis=self.is_char_in_string):
1392 return _icis(_startindex + "+%dc" % offset)
1393 return inner
1394
1395 def indent_region_event(self, event):
1396 head, tail, chars, lines = self.get_region()
1397 for pos in range(len(lines)):
1398 line = lines[pos]
1399 if line:
1400 raw, effective = classifyws(line, self.tabwidth)
1401 effective = effective + self.indentwidth
1402 lines[pos] = self._make_blanks(effective) + line[raw:]
1403 self.set_region(head, tail, chars, lines)
1404 return "break"
1405
1406 def dedent_region_event(self, event):
1407 head, tail, chars, lines = self.get_region()
1408 for pos in range(len(lines)):
1409 line = lines[pos]
1410 if line:
1411 raw, effective = classifyws(line, self.tabwidth)
1412 effective = max(effective - self.indentwidth, 0)
1413 lines[pos] = self._make_blanks(effective) + line[raw:]
1414 self.set_region(head, tail, chars, lines)
1415 return "break"
1416
1417 def comment_region_event(self, event):
1418 head, tail, chars, lines = self.get_region()
1419 for pos in range(len(lines) - 1):
1420 line = lines[pos]
1421 lines[pos] = '##' + line
1422 self.set_region(head, tail, chars, lines)
1423
1424 def uncomment_region_event(self, event):
1425 head, tail, chars, lines = self.get_region()
1426 for pos in range(len(lines)):
1427 line = lines[pos]
1428 if not line:
1429 continue
1430 if line[:2] == '##':
1431 line = line[2:]
1432 elif line[:1] == '#':
1433 line = line[1:]
1434 lines[pos] = line
1435 self.set_region(head, tail, chars, lines)
1436
1437 def tabify_region_event(self, event):
1438 head, tail, chars, lines = self.get_region()
1439 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-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)):
1442 line = lines[pos]
1443 if line:
1444 raw, effective = classifyws(line, tabwidth)
1445 ntabs, nspaces = divmod(effective, tabwidth)
1446 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1447 self.set_region(head, tail, chars, lines)
1448
1449 def untabify_region_event(self, event):
1450 head, tail, chars, lines = self.get_region()
1451 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001452 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001453 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001454 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001455 self.set_region(head, tail, chars, lines)
1456
1457 def toggle_tabs_event(self, event):
1458 if self.askyesno(
1459 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001460 "Turn tabs " + ("on", "off")[self.usetabs] +
1461 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001462 ("will be", "remains at")[self.usetabs] + " 8." +
1463 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001464 parent=self.text):
1465 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001466 # Try to prevent inconsistent indentation.
1467 # User must change indent width manually after using tabs.
1468 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001469 return "break"
1470
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001471 # XXX this isn't bound to anything -- see tabwidth comments
1472## def change_tabwidth_event(self, event):
1473## new = self._asktabwidth()
1474## if new != self.tabwidth:
1475## self.tabwidth = new
1476## self.set_indentation_params(0, guess=0)
1477## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001478
1479 def change_indentwidth_event(self, event):
1480 new = self.askinteger(
1481 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001482 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001483 parent=self.text,
1484 initialvalue=self.indentwidth,
1485 minvalue=2,
1486 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001487 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001488 self.indentwidth = new
1489 return "break"
1490
1491 def get_region(self):
1492 text = self.text
1493 first, last = self.get_selection_indices()
1494 if first and last:
1495 head = text.index(first + " linestart")
1496 tail = text.index(last + "-1c lineend +1c")
1497 else:
1498 head = text.index("insert linestart")
1499 tail = text.index("insert lineend +1c")
1500 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001501 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001502 return head, tail, chars, lines
1503
1504 def set_region(self, head, tail, chars, lines):
1505 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001506 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001507 if newchars == chars:
1508 text.bell()
1509 return
1510 text.tag_remove("sel", "1.0", "end")
1511 text.mark_set("insert", head)
1512 text.undo_block_start()
1513 text.delete(head, tail)
1514 text.insert(head, newchars)
1515 text.undo_block_stop()
1516 text.tag_add("sel", head, "insert")
1517
1518 # Make string that displays as n leading blanks.
1519
1520 def _make_blanks(self, n):
1521 if self.usetabs:
1522 ntabs, nspaces = divmod(n, self.tabwidth)
1523 return '\t' * ntabs + ' ' * nspaces
1524 else:
1525 return ' ' * n
1526
1527 # Delete from beginning of line to insert point, then reinsert
1528 # column logical (meaning use tabs if appropriate) spaces.
1529
1530 def reindent_to(self, column):
1531 text = self.text
1532 text.undo_block_start()
1533 if text.compare("insert linestart", "!=", "insert"):
1534 text.delete("insert linestart", "insert")
1535 if column:
1536 text.insert("insert", self._make_blanks(column))
1537 text.undo_block_stop()
1538
1539 def _asktabwidth(self):
1540 return self.askinteger(
1541 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001542 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001543 parent=self.text,
1544 initialvalue=self.indentwidth,
1545 minvalue=2,
Roger Serwy75b249c2013-04-06 20:26:53 -05001546 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001547
1548 # Guess indentwidth from text content.
1549 # Return guessed indentwidth. This should not be believed unless
1550 # it's in a reasonable range (e.g., it will be 0 if no indented
1551 # blocks are found).
1552
1553 def guess_indent(self):
1554 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1555 if opener and indented:
1556 raw, indentsmall = classifyws(opener, self.tabwidth)
1557 raw, indentlarge = classifyws(indented, self.tabwidth)
1558 else:
1559 indentsmall = indentlarge = 0
1560 return indentlarge - indentsmall
1561
1562# "line.col" -> line, as an int
1563def index2line(index):
1564 return int(float(index))
1565
1566# Look at the leading whitespace in s.
1567# Return pair (# of leading ws characters,
1568# effective # of leading blanks after expanding
1569# tabs to width tabwidth)
1570
1571def classifyws(s, tabwidth):
1572 raw = effective = 0
1573 for ch in s:
1574 if ch == ' ':
1575 raw = raw + 1
1576 effective = effective + 1
1577 elif ch == '\t':
1578 raw = raw + 1
1579 effective = (effective // tabwidth + 1) * tabwidth
1580 else:
1581 break
1582 return raw, effective
1583
1584import tokenize
1585_tokenize = tokenize
1586del tokenize
1587
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001588class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001589
1590 # .run() chews over the Text widget, looking for a block opener
1591 # and the stmt following it. Returns a pair,
1592 # (line containing block opener, line containing stmt)
1593 # Either or both may be None.
1594
1595 def __init__(self, text, tabwidth):
1596 self.text = text
1597 self.tabwidth = tabwidth
1598 self.i = self.finished = 0
1599 self.blkopenline = self.indentedline = None
1600
1601 def readline(self):
1602 if self.finished:
1603 return ""
1604 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001605 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001606 if self.text.compare(mark, ">=", "end"):
1607 return ""
1608 return self.text.get(mark, mark + " lineend+1c")
1609
1610 def tokeneater(self, type, token, start, end, line,
1611 INDENT=_tokenize.INDENT,
1612 NAME=_tokenize.NAME,
1613 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1614 if self.finished:
1615 pass
1616 elif type == NAME and token in OPENERS:
1617 self.blkopenline = line
1618 elif type == INDENT and self.blkopenline:
1619 self.indentedline = line
1620 self.finished = 1
1621
1622 def run(self):
1623 save_tabsize = _tokenize.tabsize
1624 _tokenize.tabsize = self.tabwidth
1625 try:
1626 try:
1627 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001628 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001629 # since we cut off the tokenizer early, we can trigger
1630 # spurious errors
1631 pass
1632 finally:
1633 _tokenize.tabsize = save_tabsize
1634 return self.blkopenline, self.indentedline
1635
1636### end autoindent code ###
1637
David Scherer7aced172000-08-15 01:13:23 +00001638def prepstr(s):
1639 # Helper to extract the underscore from a string, e.g.
1640 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001641 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001642 if i >= 0:
1643 s = s[:i] + s[i+1:]
1644 return i, s
1645
1646
1647keynames = {
1648 'bracketleft': '[',
1649 'bracketright': ']',
1650 'slash': '/',
1651}
1652
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001653def get_accelerator(keydefs, eventname):
1654 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001655 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1656 # if not keylist:
Ned Deily57847df2014-03-27 20:47:04 -07001657 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily60651532011-01-31 00:52:49 +00001658 "<<open-module>>",
1659 "<<goto-line>>",
1660 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001661 return ""
1662 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001663 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001664 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1665 s = re.sub("Key-", "", s)
1666 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1667 s = re.sub("Control-", "Ctrl-", s)
1668 s = re.sub("-", "+", s)
1669 s = re.sub("><", " ", s)
1670 s = re.sub("<", "", s)
1671 s = re.sub(">", "", s)
1672 return s
1673
1674
1675def fixwordbreaks(root):
1676 # Make sure that Tk's double-click and next/previous word
1677 # operations use our definition of a word (i.e. an identifier)
1678 tk = root.tk
1679 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1680 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1681 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1682
1683
Terry Jan Reedycf834762014-10-17 01:31:29 -04001684def _editor_window(parent): # htest #
1685 # error if close master window first - timer event, after script
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001686 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001687 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001688 if sys.argv[1:]:
1689 filename = sys.argv[1]
1690 else:
1691 filename = None
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001692 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001693 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001694 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycf834762014-10-17 01:31:29 -04001695 # Does not stop error, neither does following
1696 # edit.text.bind("<<close-window>>", edit.close_event)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001697
David Scherer7aced172000-08-15 01:13:23 +00001698
1699if __name__ == '__main__':
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001700 from idlelib.idle_test.htest import run
Terry Jan Reedy70e763c2015-09-20 19:55:44 -04001701 run(_editor_window)