blob: fd7958f8ba732cf36697d27c07b9cf520ed7bf8c [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
David Scherer7aced172000-08-15 01:13:23 +000020
21# The default tab setting for a Text widget, in average-width characters.
22TK_TABWIDTH_DEFAULT = 8
23
Terry Jan Reedyc11633e2014-08-14 21:54:38 -040024_py_version = ' (%s)' % platform.python_version()
25
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000026def _sphinx_version():
27 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
28 major, minor, micro, level, serial = sys.version_info
29 release = '%s%s' % (major, minor)
30 if micro:
Benjamin Petersonfb234632009-06-13 15:42:23 +000031 release += '%s' % (micro,)
32 if level == 'candidate':
33 release += 'rc%s' % (serial,)
34 elif level != 'final':
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000035 release += '%s%s' % (level[0], serial)
36 return release
37
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000038def _find_module(fullname, path=None):
39 """Version of imp.find_module() that handles hierarchical module names"""
40
41 file = None
42 for tgt in fullname.split('.'):
43 if file is not None:
44 file.close() # close intermediate files
45 (file, filename, descr) = imp.find_module(tgt, path)
46 if descr[2] == imp.PY_SOURCE:
47 break # find but not load the source file
48 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000049 try:
50 path = module.__path__
51 except AttributeError:
52 raise ImportError, 'No source for module ' + module.__name__
Raymond Hettinger179816d2011-04-12 18:54:46 -070053 if descr[2] != imp.PY_SOURCE:
54 # If all of the above fails and didn't raise an exception,fallback
55 # to a straight import which can find __init__.py in a package.
56 m = __import__(fullname)
57 try:
58 filename = m.__file__
59 except AttributeError:
60 pass
61 else:
62 file = None
63 base, ext = os.path.splitext(filename)
64 if ext == '.pyc':
65 ext = '.py'
66 filename = base + ext
67 descr = filename, None, imp.PY_SOURCE
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000068 return file, filename, descr
69
Terry Jan Reedyadb87e22012-02-05 15:10:55 -050070
71class HelpDialog(object):
72
73 def __init__(self):
74 self.parent = None # parent of help window
75 self.dlg = None # the help window iteself
76
77 def display(self, parent, near=None):
78 """ Display the help dialog.
79
80 parent - parent widget for the help window
81
82 near - a Toplevel widget (e.g. EditorWindow or PyShell)
83 to use as a reference for placing the help window
84 """
85 if self.dlg is None:
86 self.show_dialog(parent)
87 if near:
88 self.nearwindow(near)
89
90 def show_dialog(self, parent):
91 self.parent = parent
92 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
93 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
94 dlg.bind('<Destroy>', self.destroy, '+')
95
96 def nearwindow(self, near):
97 # Place the help dialog near the window specified by parent.
98 # Note - this may not reposition the window in Metacity
99 # if "/apps/metacity/general/disable_workarounds" is enabled
100 dlg = self.dlg
101 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
102 dlg.withdraw()
103 dlg.geometry("=+%d+%d" % geom)
104 dlg.deiconify()
105 dlg.lift()
106
107 def destroy(self, ev=None):
108 self.dlg = None
109 self.parent = None
110
111helpDialog = HelpDialog() # singleton instance
Terry Jan Reedy43458462014-05-19 00:12:00 -0400112def _help_dialog(parent): # wrapper for htest
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -0400113 helpDialog.show_dialog(parent)
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500114
115
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000116class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +0000117 from idlelib.Percolator import Percolator
118 from idlelib.ColorDelegator import ColorDelegator
119 from idlelib.UndoDelegator import UndoDelegator
120 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
121 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +0000122 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +0000123 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000124
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000125 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000126
127 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000128 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +0000129 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000130 if sys.platform.count('linux'):
131 # look for html docs in a couple of standard places
132 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
133 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
134 dochome = '/var/www/html/python/index.html'
135 else:
136 basepath = '/usr/share/doc/' # standard location
137 dochome = os.path.join(basepath, pyver,
138 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000139 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +0000140 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +0000141 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000142 if os.path.isfile(chmfile):
143 dochome = chmfile
Ned Deily57847df2014-03-27 20:47:04 -0700144 elif sys.platform == 'darwin':
145 # documentation may be stored inside a python framework
Ronald Oussoren19302d92006-06-11 14:33:36 +0000146 dochome = os.path.join(sys.prefix,
147 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000148 dochome = os.path.normpath(dochome)
149 if os.path.isfile(dochome):
150 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +0000151 if sys.platform == 'darwin':
152 # Safari requires real file:-URLs
153 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000154 else:
Terry Jan Reedyb17c1e02014-09-19 22:54:09 -0400155 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
David Scherer7aced172000-08-15 01:13:23 +0000156 self.flist = flist
157 root = root or flist.root
158 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +0000159 try:
160 sys.ps1
161 except AttributeError:
162 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000163 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000164 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000165 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000166 self.tkinter_vars = flist.vars
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200167 #self.top.instance_dict makes flist.inversedict available to
168 #configDialog.py so it can access all EditorWindow instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000169 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000170 else:
171 self.tkinter_vars = {} # keys: Tkinter event names
172 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000173 self.top.instance_dict = {}
174 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000175 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000176 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000177 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200178 self.width = idleConf.GetOption('main','EditorWindow','width', type='int')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000179 text_options = {
180 'name': 'text',
181 'padx': 5,
182 'wrap': 'none',
183 'width': self.width,
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200184 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')}
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000185 if TkVersion >= 8.5:
186 # Starting with tk 8.5 we have to set the new tabstyle option
187 # to 'wordprocessor' to achieve the same display of tabs as in
188 # older tk versions.
189 text_options['tabstyle'] = 'wordprocessor'
190 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000191 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000192
193 self.createmenubar()
194 self.apply_bindings()
195
196 self.top.protocol("WM_DELETE_WINDOW", self.close)
197 self.top.bind("<<close-window>>", self.close_event)
Ned Deily57847df2014-03-27 20:47:04 -0700198 if macosxSupport.isAquaTk():
Ronald Oussoren17db4952006-07-23 09:41:09 +0000199 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000200 text.bind('<<close-window>>', self.close_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000201 # Some OS X systems have only one mouse button,
202 # so use control-click for pulldown menus there.
203 # (Note, AquaTk defines <2> as the right button if
204 # present and the Tk Text widget already binds <2>.)
205 text.bind("<Control-Button-1>",self.right_menu_event)
206 else:
207 # Elsewhere, use right-click for pulldown menus.
208 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000209 text.bind("<<cut>>", self.cut)
210 text.bind("<<copy>>", self.copy)
211 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000212 text.bind("<<center-insert>>", self.center_insert_event)
213 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000214 text.bind("<<python-docs>>", self.python_docs)
215 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000216 text.bind("<<open-config-dialog>>", self.config_dialog)
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400217 text.bind("<<open-config-extensions-dialog>>",
218 self.config_extensions_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000219 text.bind("<<open-module>>", self.open_module)
220 text.bind("<<do-nothing>>", lambda event: "break")
221 text.bind("<<select-all>>", self.select_all)
222 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000223 text.bind("<<find>>", self.find_event)
224 text.bind("<<find-again>>", self.find_again_event)
225 text.bind("<<find-in-files>>", self.find_in_files_event)
226 text.bind("<<find-selection>>", self.find_selection_event)
227 text.bind("<<replace>>", self.replace_event)
228 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000229 text.bind("<<smart-backspace>>",self.smart_backspace_event)
230 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
231 text.bind("<<smart-indent>>",self.smart_indent_event)
232 text.bind("<<indent-region>>",self.indent_region_event)
233 text.bind("<<dedent-region>>",self.dedent_region_event)
234 text.bind("<<comment-region>>",self.comment_region_event)
235 text.bind("<<uncomment-region>>",self.uncomment_region_event)
236 text.bind("<<tabify-region>>",self.tabify_region_event)
237 text.bind("<<untabify-region>>",self.untabify_region_event)
238 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
239 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000240 text.bind("<Left>", self.move_at_edge_if_selection(0))
241 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000242 text.bind("<<del-word-left>>", self.del_word_left)
243 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000244 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000245
David Scherer7aced172000-08-15 01:13:23 +0000246 if flist:
247 flist.inversedict[self] = key
248 if key:
249 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000250 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000251 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
252 text.bind("<<open-class-browser>>", self.open_class_browser)
253 text.bind("<<open-path-browser>>", self.open_path_browser)
254
Steven M. Gava898a3652001-10-07 11:10:44 +0000255 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000256 vbar['command'] = text.yview
257 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000258 text['yscrollcommand'] = vbar.set
Terry Jan Reedy12352142015-08-01 18:57:27 -0400259 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000260 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
261 text.pack(side=TOP, fill=BOTH, expand=1)
262 text.focus_set()
263
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000264 # usetabs true -> literal tab characters are used by indent and
265 # dedent cmds, possibly mixed with spaces if
266 # indentwidth is not a multiple of tabwidth,
267 # which will cause Tabnanny to nag!
268 # false -> tab characters are converted to spaces by indent
269 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000270 # Although use-spaces=0 can be configured manually in config-main.def,
271 # configuration of tabs v. spaces is not supported in the configuration
272 # dialog. IDLE promotes the preferred Python indentation: use spaces!
273 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
274 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000275
276 # tabwidth is the display width of a literal tab character.
277 # CAUTION: telling Tk to use anything other than its default
278 # tab setting causes it to use an entirely different tabbing algorithm,
279 # treating tab stops as fixed distances from the left margin.
280 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000281 self.tabwidth = 8 # must remain 8 until Tk is fixed.
282
283 # indentwidth is the number of screen characters per indent level.
284 # The recommended Python indentation is four spaces.
285 self.indentwidth = self.tabwidth
286 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000287
288 # If context_use_ps1 is true, parsing searches back for a ps1 line;
289 # else searches for a popular (if, def, ...) Python stmt.
290 self.context_use_ps1 = False
291
292 # When searching backwards for a reliable place to begin parsing,
293 # first start num_context_lines[0] lines back, then
294 # num_context_lines[1] lines back if that didn't work, and so on.
295 # The last value should be huge (larger than the # of lines in a
296 # conceivable file).
297 # Making the initial values larger slows things down more often.
298 self.num_context_lines = 50, 500, 5000000
299
David Scherer7aced172000-08-15 01:13:23 +0000300 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000301
302 self.undo = undo = self.UndoDelegator()
303 per.insertfilter(undo)
304 text.undo_block_start = undo.undo_block_start
305 text.undo_block_stop = undo.undo_block_stop
306 undo.set_saved_change_hook(self.saved_change_hook)
307
308 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000309 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000310 io.set_filename_change_hook(self.filename_change_hook)
311
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000312 # Create the recent files submenu
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400313 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000314 self.menudict['file'].insert_cascade(3, label='Recent Files',
315 underline=0,
316 menu=self.recent_files_menu)
317 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000318
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000319 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000320 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000321 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000322 io.loadfile(filename)
323 else:
324 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000325 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000326 self.saved_change_hook()
327
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000328 self.set_indentation_params(self.ispythonsource(filename))
329
David Scherer7aced172000-08-15 01:13:23 +0000330 self.load_extensions()
331
332 menu = self.menudict.get('windows')
333 if menu:
334 end = menu.index("end")
335 if end is None:
336 end = -1
337 if end >= 0:
338 menu.add_separator()
339 end = end + 1
340 self.wmenu_end = end
341 WindowList.register_callback(self.postwindowsmenu)
342
343 # Some abstractions so IDLE extensions are cross-IDE
344 self.askyesno = tkMessageBox.askyesno
345 self.askinteger = tkSimpleDialog.askinteger
346 self.showerror = tkMessageBox.showerror
347
Roger Serwy02c0ed02013-05-20 22:13:39 -0500348 self._highlight_workaround() # Fix selection tags on Windows
349
350 def _highlight_workaround(self):
351 # On Windows, Tk removes painting of the selection
352 # tags which is different behavior than on Linux and Mac.
353 # See issue14146 for more information.
354 if not sys.platform.startswith('win'):
355 return
356
357 text = self.text
358 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
359 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
360 def highlight_fix(focus):
361 sel_range = text.tag_ranges("sel")
362 if sel_range:
363 if focus == 'out':
364 HILITE_CONFIG = idleConf.GetHighlight(
365 idleConf.CurrentTheme(), 'hilite')
366 text.tag_config("sel_fix", HILITE_CONFIG)
367 text.tag_raise("sel_fix")
368 text.tag_add("sel_fix", *sel_range)
369 elif focus == 'in':
370 text.tag_remove("sel_fix", "1.0", "end")
371
372 text.bind("<<Highlight-FocusOut>>",
373 lambda ev: highlight_fix("out"))
374 text.bind("<<Highlight-FocusIn>>",
375 lambda ev: highlight_fix("in"))
376
377
Martin v. Löwis307021f2005-11-27 16:59:04 +0000378 def _filename_to_unicode(self, filename):
379 """convert filename to unicode in order to display it in Tk"""
380 if isinstance(filename, unicode) or not filename:
381 return filename
382 else:
383 try:
384 return filename.decode(self.filesystemencoding)
385 except UnicodeDecodeError:
386 # XXX
387 try:
388 return filename.decode(self.encoding)
389 except UnicodeDecodeError:
390 # byte-to-byte conversion
391 return filename.decode('iso8859-1')
392
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000393 def new_callback(self, event):
394 dirname, basename = self.io.defaultfilename()
395 self.flist.new(dirname)
396 return "break"
397
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000398 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400399 if (event.state & 4) != 0 and event.keysym == "Home":
400 # state&4==Control. If <Control-Home>, use the Tk binding.
401 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000402 if self.text.index("iomark") and \
403 self.text.compare("iomark", "<=", "insert lineend") and \
404 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400405 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000406 insertpt = int(self.text.index("iomark").split(".")[1])
407 else:
408 line = self.text.get("insert linestart", "insert lineend")
409 for insertpt in xrange(len(line)):
410 if line[insertpt] not in (' ','\t'):
411 break
412 else:
413 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000414 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000415 if insertpt == lineat:
416 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000417 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000418 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400419 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000420 self.text.tag_remove("sel", "1.0", "end")
421 else:
422 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400423 self.text.mark_set("my_anchor", "insert") # there was no previous selection
424 else:
425 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
426 self.text.mark_set("my_anchor", "sel.first") # extend back
427 else:
428 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000429 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400430 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000431 if self.text.compare(first,">",last):
432 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000433 self.text.tag_remove("sel", "1.0", "end")
434 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000435 self.text.mark_set("insert", dest)
436 self.text.see("insert")
437 return "break"
438
David Scherer7aced172000-08-15 01:13:23 +0000439 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000440 self.status_bar = self.MultiStatusBar(self.top)
Ned Deily57847df2014-03-27 20:47:04 -0700441 if sys.platform == "darwin":
Ronald Oussoren19302d92006-06-11 14:33:36 +0000442 # Insert some padding to avoid obscuring some of the statusbar
443 # by the resize widget.
444 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000445 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
446 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
447 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000448 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
449 self.text.event_add("<<set-line-and-column>>",
450 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000451 self.text.after_idle(self.set_line_and_column)
452
453 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000454 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000455 self.status_bar.set_label('column', 'Col: %s' % column)
456 self.status_bar.set_label('line', 'Ln: %s' % line)
457
David Scherer7aced172000-08-15 01:13:23 +0000458 menu_specs = [
459 ("file", "_File"),
460 ("edit", "_Edit"),
461 ("format", "F_ormat"),
462 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000463 ("options", "_Options"),
Ned Deilyb5daa3d2015-01-17 21:03:41 -0800464 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000465 ("help", "_Help"),
466 ]
467
Ronald Oussoren19302d92006-06-11 14:33:36 +0000468
David Scherer7aced172000-08-15 01:13:23 +0000469 def createmenubar(self):
470 mbar = self.menubar
471 self.menudict = menudict = {}
472 for name, label in self.menu_specs:
473 underline, label = prepstr(label)
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400474 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000475 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000476
Ned Deily57847df2014-03-27 20:47:04 -0700477 if macosxSupport.isCarbonTk():
Ronald Oussoren19302d92006-06-11 14:33:36 +0000478 # Insert the application menu
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400479 menudict['application'] = menu = Menu(mbar, name='apple',
480 tearoff=0)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000481 mbar.add_cascade(label='IDLE', menu=menu)
482
David Scherer7aced172000-08-15 01:13:23 +0000483 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000484 self.base_helpmenu_length = self.menudict['help'].index(END)
485 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000486
487 def postwindowsmenu(self):
488 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000489 menu = self.menudict['windows']
490 end = menu.index("end")
491 if end is None:
492 end = -1
493 if end > self.wmenu_end:
494 menu.delete(self.wmenu_end+1, end)
495 WindowList.add_windows_to_menu(menu)
496
497 rmenu = None
498
499 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000500 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
501 if not self.rmenu:
502 self.make_rmenu()
503 rmenu = self.rmenu
504 self.event = event
505 iswin = sys.platform[:3] == 'win'
506 if iswin:
507 self.text.config(cursor="arrow")
Andrew Svetlov5018db72012-11-01 22:39:14 +0200508
Roger Serwy231a8fd2013-04-07 12:15:52 -0500509 for item in self.rmenu_specs:
510 try:
511 label, eventname, verify_state = item
512 except ValueError: # see issue1207589
513 continue
514
Andrew Svetlov5018db72012-11-01 22:39:14 +0200515 if verify_state is None:
516 continue
517 state = getattr(self, verify_state)()
518 rmenu.entryconfigure(label, state=state)
519
David Scherer7aced172000-08-15 01:13:23 +0000520 rmenu.tk_popup(event.x_root, event.y_root)
521 if iswin:
522 self.text.config(cursor="ibeam")
523
524 rmenu_specs = [
Andrew Svetlov5018db72012-11-01 22:39:14 +0200525 # ("Label", "<<virtual-event>>", "statefuncname"), ...
526 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000527 ]
528
529 def make_rmenu(self):
530 rmenu = Menu(self.text, tearoff=0)
Roger Serwy231a8fd2013-04-07 12:15:52 -0500531 for item in self.rmenu_specs:
532 label, eventname = item[0], item[1]
Andrew Svetlov5018db72012-11-01 22:39:14 +0200533 if label is not None:
534 def command(text=self.text, eventname=eventname):
535 text.event_generate(eventname)
536 rmenu.add_command(label=label, command=command)
537 else:
538 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000539 self.rmenu = rmenu
540
Andrew Svetlov5018db72012-11-01 22:39:14 +0200541 def rmenu_check_cut(self):
542 return self.rmenu_check_copy()
543
544 def rmenu_check_copy(self):
545 try:
546 indx = self.text.index('sel.first')
547 except TclError:
548 return 'disabled'
549 else:
550 return 'normal' if indx else 'disabled'
551
552 def rmenu_check_paste(self):
553 try:
554 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
555 except TclError:
556 return 'disabled'
557 else:
558 return 'normal'
559
David Scherer7aced172000-08-15 01:13:23 +0000560 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000561 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000562
Steven M. Gava3b55a892001-11-21 05:56:26 +0000563 def config_dialog(self, event=None):
564 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400565 def config_extensions_dialog(self, event=None):
566 configDialog.ConfigExtensionsDialog(self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000567
David Scherer7aced172000-08-15 01:13:23 +0000568 def help_dialog(self, event=None):
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500569 if self.root:
570 parent = self.root
571 else:
572 parent = self.top
573 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000574
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000575 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000576 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000577 try:
578 os.startfile(self.help_url)
579 except WindowsError as why:
580 tkMessageBox.showerror(title='Document Start Failure',
581 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000582 else:
583 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000584 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000585
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000586 def cut(self,event):
587 self.text.event_generate("<<Cut>>")
588 return "break"
589
590 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000591 if not self.text.tag_ranges("sel"):
592 # There is no selection, so do nothing and maybe interrupt.
593 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000594 self.text.event_generate("<<Copy>>")
595 return "break"
596
597 def paste(self,event):
598 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000599 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000600 return "break"
601
David Scherer7aced172000-08-15 01:13:23 +0000602 def select_all(self, event=None):
603 self.text.tag_add("sel", "1.0", "end-1c")
604 self.text.mark_set("insert", "1.0")
605 self.text.see("insert")
606 return "break"
607
608 def remove_selection(self, event=None):
609 self.text.tag_remove("sel", "1.0", "end")
610 self.text.see("insert")
611
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000612 def move_at_edge_if_selection(self, edge_index):
613 """Cursor move begins at start or end of selection
614
615 When a left/right cursor key is pressed create and return to Tkinter a
616 function which causes a cursor move from the associated edge of the
617 selection.
618
619 """
620 self_text_index = self.text.index
621 self_text_mark_set = self.text.mark_set
622 edges_table = ("sel.first+1c", "sel.last-1c")
623 def move_at_edge(event):
624 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
625 try:
626 self_text_index("sel.first")
627 self_text_mark_set("insert", edges_table[edge_index])
628 except TclError:
629 pass
630 return move_at_edge
631
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000632 def del_word_left(self, event):
633 self.text.event_generate('<Meta-Delete>')
634 return "break"
635
636 def del_word_right(self, event):
637 self.text.event_generate('<Meta-d>')
638 return "break"
639
Steven M. Gavac5976402002-01-04 03:06:08 +0000640 def find_event(self, event):
641 SearchDialog.find(self.text)
642 return "break"
643
644 def find_again_event(self, event):
645 SearchDialog.find_again(self.text)
646 return "break"
647
648 def find_selection_event(self, event):
649 SearchDialog.find_selection(self.text)
650 return "break"
651
652 def find_in_files_event(self, event):
653 GrepDialog.grep(self.text, self.io, self.flist)
654 return "break"
655
656 def replace_event(self, event):
657 ReplaceDialog.replace(self.text)
658 return "break"
659
660 def goto_line_event(self, event):
661 text = self.text
662 lineno = tkSimpleDialog.askinteger("Goto",
663 "Go to line number:",parent=text)
664 if lineno is None:
665 return "break"
666 if lineno <= 0:
667 text.bell()
668 return "break"
669 text.mark_set("insert", "%d.0" % lineno)
670 text.see("insert")
671
David Scherer7aced172000-08-15 01:13:23 +0000672 def open_module(self, event=None):
673 # XXX Shouldn't this be in IOBinding or in FileList?
674 try:
675 name = self.text.get("sel.first", "sel.last")
676 except TclError:
677 name = ""
678 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000679 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000680 name = tkSimpleDialog.askstring("Module",
681 "Enter the name of a Python module\n"
682 "to search on sys.path and open:",
683 parent=self.text, initialvalue=name)
684 if name:
685 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000686 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000687 return
David Scherer7aced172000-08-15 01:13:23 +0000688 # XXX Ought to insert current file's directory in front of path
689 try:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400690 (f, file_path, (suffix, mode, mtype)) = _find_module(name)
Terry Jan Reedy2b149862013-06-29 00:59:34 -0400691 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000692 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
693 return
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400694 if mtype != imp.PY_SOURCE:
David Scherer7aced172000-08-15 01:13:23 +0000695 tkMessageBox.showerror("Unsupported type",
696 "%s is not a source module" % name, parent=self.text)
697 return
698 if f:
699 f.close()
700 if self.flist:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400701 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000702 else:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400703 self.io.loadfile(file_path)
704 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000705
706 def open_class_browser(self, event=None):
707 filename = self.io.filename
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400708 if not (self.__class__.__name__ == 'PyShellEditorWindow'
709 and filename):
710 filename = self.open_module()
711 if filename is None:
712 return
David Scherer7aced172000-08-15 01:13:23 +0000713 head, tail = os.path.split(filename)
714 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000715 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000716 ClassBrowser.ClassBrowser(self.flist, base, [head])
717
718 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000719 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000720 PathBrowser.PathBrowser(self.flist)
721
722 def gotoline(self, lineno):
723 if lineno is not None and lineno > 0:
724 self.text.mark_set("insert", "%d.0" % lineno)
725 self.text.tag_remove("sel", "1.0", "end")
726 self.text.tag_add("sel", "insert", "insert +1l")
727 self.center()
728
729 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000730 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000731 return True
David Scherer7aced172000-08-15 01:13:23 +0000732 base, ext = os.path.splitext(os.path.basename(filename))
733 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000734 return True
David Scherer7aced172000-08-15 01:13:23 +0000735 try:
736 f = open(filename)
737 line = f.readline()
738 f.close()
739 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000740 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000741 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000742
743 def close_hook(self):
744 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000745 self.flist.unregister_maybe_terminate(self)
746 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000747
748 def set_close_hook(self, close_hook):
749 self.close_hook = close_hook
750
751 def filename_change_hook(self):
752 if self.flist:
753 self.flist.filename_changed_edit(self)
754 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000755 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000756 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000757
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000758 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000759 if self.color:
760 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000761 if self.ispythonsource(self.io.filename):
762 self.color = self.ColorDelegator()
763 # can add more colorizers here...
764 if self.color:
765 self.per.removefilter(self.undo)
766 self.per.insertfilter(self.color)
767 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000768
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000769 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000770 if not self.color:
771 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000772 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000773 self.per.removefilter(self.color)
774 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000775
Steven M. Gavab77d3432002-03-02 07:16:21 +0000776 def ResetColorizer(self):
Terry Jan Reedy13755382014-10-09 18:44:26 -0400777 "Update the color theme"
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000778 # Called from self.filename_change_hook and from configDialog.py
779 self._rmcolorizer()
780 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000781 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000782 normal_colors = idleConf.GetHighlight(theme, 'normal')
783 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
784 select_colors = idleConf.GetHighlight(theme, 'hilite')
785 self.text.config(
786 foreground=normal_colors['foreground'],
787 background=normal_colors['background'],
788 insertbackground=cursor_color,
789 selectforeground=select_colors['foreground'],
790 selectbackground=select_colors['background'],
791 )
David Scherer7aced172000-08-15 01:13:23 +0000792
Steven M. Gavab1585412002-03-12 00:21:56 +0000793 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000794 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000795 # Called from configDialog.py
Terry Jan Reedy12352142015-08-01 18:57:27 -0400796
797 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000798
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000799 def RemoveKeybindings(self):
800 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000801 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000802 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000803 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000804 self.text.event_delete(event, *keylist)
805 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000806 xkeydefs = idleConf.GetExtensionBindings(extensionName)
807 if xkeydefs:
808 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000809 self.text.event_delete(event, *keylist)
810
811 def ApplyKeybindings(self):
812 "Update the keybindings after they are changed"
813 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000814 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000815 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000816 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000817 xkeydefs = idleConf.GetExtensionBindings(extensionName)
818 if xkeydefs:
819 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000820 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000821 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000822 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000823 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000824 for item in menu[1]:
825 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000826 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000827 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000828 menu = self.menudict[menubarItem]
Ned Deily14ef0c82013-07-20 14:38:24 -0700829 end = menu.index(END)
830 if end is None:
831 # Skip empty menus
832 continue
833 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000834 for index in range(0, end):
835 if menu.type(index) == 'command':
836 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000837 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000838 itemName = menu.entrycget(index, 'label')
839 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000840 if menubarItem in menuEventDict:
841 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000842 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000843 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000844 accel = get_accelerator(keydefs, event)
845 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000846
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000847 def set_notabs_indentwidth(self):
848 "Update the indentwidth if changed and not using tabs in this window"
849 # Called from configDialog.py
850 if not self.usetabs:
851 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
852 type='int')
853
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000854 def reset_help_menu_entries(self):
855 "Update the additional help entries on the Help menu"
856 help_list = idleConf.GetAllExtraHelpSourcesList()
857 helpmenu = self.menudict['help']
858 # first delete the extra help entries, if any
859 helpmenu_length = helpmenu.index(END)
860 if helpmenu_length > self.base_helpmenu_length:
861 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
862 # then rebuild them
863 if help_list:
864 helpmenu.add_separator()
865 for entry in help_list:
866 cmd = self.__extra_help_callback(entry[1])
867 helpmenu.add_command(label=entry[0], command=cmd)
868 # and update the menu dictionary
869 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000870
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000871 def __extra_help_callback(self, helpfile):
872 "Create a callback with the helpfile value frozen at definition time"
873 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000874 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000875 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000876 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000877 try:
878 os.startfile(helpfile)
879 except WindowsError as why:
880 tkMessageBox.showerror(title='Document Start Failure',
881 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000882 else:
883 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000884 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000885
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000886 def update_recent_files_list(self, new_file=None):
887 "Load and update the recent files list and menus"
888 rf_list = []
889 if os.path.exists(self.recent_files_path):
Terry Jan Reedyf9489432013-08-04 15:39:56 -0400890 with open(self.recent_files_path, 'r') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000891 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000892 if new_file:
893 new_file = os.path.abspath(new_file) + '\n'
894 if new_file in rf_list:
895 rf_list.remove(new_file) # move to top
896 rf_list.insert(0, new_file)
897 # clean and save the recent files list
898 bad_paths = []
899 for path in rf_list:
900 if '\0' in path or not os.path.exists(path[0:-1]):
901 bad_paths.append(path)
902 rf_list = [path for path in rf_list if path not in bad_paths]
903 ulchars = "1234567890ABCDEFGHIJK"
904 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000905 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800906 with open(self.recent_files_path, 'w') as rf_file:
907 rf_file.writelines(rf_list)
908 except IOError as err:
909 if not getattr(self.root, "recentfilelist_error_displayed", False):
910 self.root.recentfilelist_error_displayed = True
911 tkMessageBox.showerror(title='IDLE Error',
912 message='Unable to update Recent Files list:\n%s'
913 % str(err),
914 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000915 # for each edit window instance, construct the recent files menu
916 for instance in self.top.instance_dict.keys():
917 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700918 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000919 for i, file_name in enumerate(rf_list):
920 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000921 # make unicode string to display non-ASCII chars correctly
922 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000923 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000924 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000925 command=callback,
926 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000927
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000928 def __recent_file_callback(self, file_name):
929 def open_recent_file(fn_closure=file_name):
930 self.io.open(editFile=fn_closure)
931 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000932
David Scherer7aced172000-08-15 01:13:23 +0000933 def saved_change_hook(self):
934 short = self.short_title()
935 long = self.long_title()
936 if short and long:
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400937 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000938 elif short:
939 title = short
940 elif long:
941 title = long
942 else:
943 title = "Untitled"
944 icon = short or long or title
945 if not self.get_saved():
946 title = "*%s*" % title
947 icon = "*%s" % icon
948 self.top.wm_title(title)
949 self.top.wm_iconname(icon)
950
951 def get_saved(self):
952 return self.undo.get_saved()
953
954 def set_saved(self, flag):
955 self.undo.set_saved(flag)
956
957 def reset_undo(self):
958 self.undo.reset_undo()
959
960 def short_title(self):
961 filename = self.io.filename
962 if filename:
963 filename = os.path.basename(filename)
Terry Jan Reedy59243652014-01-23 00:36:37 -0500964 else:
965 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000966 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400967 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000968
969 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000970 # return unicode string to display non-ASCII chars correctly
971 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000972
973 def center_insert_event(self, event):
974 self.center()
975
976 def center(self, mark="insert"):
977 text = self.text
978 top, bot = self.getwindowlines()
979 lineno = self.getlineno(mark)
980 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000981 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000982 text.yview(float(newtop))
983
984 def getwindowlines(self):
985 text = self.text
986 top = self.getlineno("@0,0")
987 bot = self.getlineno("@0,65535")
988 if top == bot and text.winfo_height() == 1:
989 # Geometry manager hasn't run yet
990 height = int(text['height'])
991 bot = top + height - 1
992 return top, bot
993
994 def getlineno(self, mark="insert"):
995 text = self.text
996 return int(float(text.index(mark)))
997
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000998 def get_geometry(self):
999 "Return (width, height, x, y)"
1000 geom = self.top.wm_geometry()
1001 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
1002 tuple = (map(int, m.groups()))
1003 return tuple
1004
David Scherer7aced172000-08-15 01:13:23 +00001005 def close_event(self, event):
1006 self.close()
1007
1008 def maybesave(self):
1009 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001010 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001011 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001012 self.top.deiconify()
1013 self.top.lower()
1014 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001015 return self.io.maybesave()
1016
1017 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001018 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +00001019 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001020 self._close()
1021 return reply
1022
1023 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001024 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001025 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001026 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001027 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001028 self.io.close()
1029 self.io = None
1030 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001031 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001032 self.color.close(False)
1033 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001034 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001035 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001036 self.per.close()
1037 self.per = None
1038 self.top.destroy()
1039 if self.close_hook:
1040 # unless override: unregister from flist, terminate if last window
1041 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001042
1043 def load_extensions(self):
1044 self.extensions = {}
1045 self.load_standard_extensions()
1046
1047 def unload_extensions(self):
1048 for ins in self.extensions.values():
1049 if hasattr(ins, "close"):
1050 ins.close()
1051 self.extensions = {}
1052
1053 def load_standard_extensions(self):
1054 for name in self.get_standard_extension_names():
1055 try:
1056 self.load_extension(name)
1057 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001058 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001059 import traceback
1060 traceback.print_exc()
1061
1062 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001063 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001064
1065 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001066 try:
1067 mod = __import__(name, globals(), locals(), [])
1068 except ImportError:
1069 print "\nFailed to import extension: ", name
1070 return
David Scherer7aced172000-08-15 01:13:23 +00001071 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001072 keydefs = idleConf.GetExtensionBindings(name)
1073 if hasattr(cls, "menudefs"):
1074 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001075 ins = cls(self)
1076 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001077 if keydefs:
1078 self.apply_bindings(keydefs)
1079 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001080 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001081 while methodname[:1] == '<':
1082 methodname = methodname[1:]
1083 while methodname[-1:] == '>':
1084 methodname = methodname[:-1]
1085 methodname = methodname + "_event"
1086 if hasattr(ins, methodname):
1087 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001088
1089 def apply_bindings(self, keydefs=None):
1090 if keydefs is None:
1091 keydefs = self.Bindings.default_keydefs
1092 text = self.text
1093 text.keydefs = keydefs
1094 for event, keylist in keydefs.items():
1095 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001096 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001097
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001098 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001099 """Add appropriate entries to the menus and submenus
1100
1101 Menus that are absent or None in self.menudict are ignored.
1102 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001103 if menudefs is None:
1104 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001105 if keydefs is None:
1106 keydefs = self.Bindings.default_keydefs
1107 menudict = self.menudict
1108 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001109 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001110 menu = menudict.get(mname)
1111 if not menu:
1112 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001113 for entry in entrylist:
1114 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001115 menu.add_separator()
1116 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001117 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001118 checkbutton = (label[:1] == '!')
1119 if checkbutton:
1120 label = label[1:]
1121 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001122 accelerator = get_accelerator(keydefs, eventname)
1123 def command(text=text, eventname=eventname):
1124 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001125 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001126 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001127 menu.add_checkbutton(label=label, underline=underline,
1128 command=command, accelerator=accelerator,
1129 variable=var)
1130 else:
1131 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001132 command=command,
1133 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001134
1135 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001136 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001137 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001138 value = var.get()
1139 return value
1140 else:
1141 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001142
1143 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001144 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001145 if var:
1146 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001147 else:
1148 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001149
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001150 def get_var_obj(self, name, vartype=None):
1151 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001152 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001153 # create a Tkinter variable object with self.text as master:
1154 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001155 return var
1156
1157 # Tk implementations of "virtual text methods" -- each platform
1158 # reusing IDLE's support code needs to define these for its GUI's
1159 # flavor of widget.
1160
1161 # Is character at text_index in a Python string? Return 0 for
1162 # "guaranteed no", true for anything else. This info is expensive
1163 # to compute ab initio, but is probably already known by the
1164 # platform's colorizer.
1165
1166 def is_char_in_string(self, text_index):
1167 if self.color:
1168 # Return true iff colorizer hasn't (re)gotten this far
1169 # yet, or the character is tagged as being in a string
1170 return self.text.tag_prevrange("TODO", text_index) or \
1171 "STRING" in self.text.tag_names(text_index)
1172 else:
1173 # The colorizer is missing: assume the worst
1174 return 1
1175
1176 # If a selection is defined in the text widget, return (start,
1177 # end) as Tkinter text indices, otherwise return (None, None)
1178 def get_selection_indices(self):
1179 try:
1180 first = self.text.index("sel.first")
1181 last = self.text.index("sel.last")
1182 return first, last
1183 except TclError:
1184 return None, None
1185
1186 # Return the text widget's current view of what a tab stop means
1187 # (equivalent width in spaces).
1188
1189 def get_tabwidth(self):
1190 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1191 return int(current)
1192
1193 # Set the text widget's current view of what a tab stop means.
1194
1195 def set_tabwidth(self, newtabwidth):
1196 text = self.text
1197 if self.get_tabwidth() != newtabwidth:
1198 pixels = text.tk.call("font", "measure", text["font"],
1199 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001200 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001201 text.configure(tabs=pixels)
1202
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001203 # If ispythonsource and guess are true, guess a good value for
1204 # indentwidth based on file content (if possible), and if
1205 # indentwidth != tabwidth set usetabs false.
1206 # In any case, adjust the Text widget's view of what a tab
1207 # character means.
1208
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001209 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001210 if guess and ispythonsource:
1211 i = self.guess_indent()
1212 if 2 <= i <= 8:
1213 self.indentwidth = i
1214 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001215 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001216 self.set_tabwidth(self.tabwidth)
1217
1218 def smart_backspace_event(self, event):
1219 text = self.text
1220 first, last = self.get_selection_indices()
1221 if first and last:
1222 text.delete(first, last)
1223 text.mark_set("insert", first)
1224 return "break"
1225 # Delete whitespace left, until hitting a real char or closest
1226 # preceding virtual tab stop.
1227 chars = text.get("insert linestart", "insert")
1228 if chars == '':
1229 if text.compare("insert", ">", "1.0"):
1230 # easy: delete preceding newline
1231 text.delete("insert-1c")
1232 else:
1233 text.bell() # at start of buffer
1234 return "break"
1235 if chars[-1] not in " \t":
1236 # easy: delete preceding real char
1237 text.delete("insert-1c")
1238 return "break"
1239 # Ick. It may require *inserting* spaces if we back up over a
1240 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001241 tabwidth = self.tabwidth
1242 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001243 assert have > 0
1244 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001245 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001246 if self.context_use_ps1:
1247 last_line_of_prompt = sys.ps1.split('\n')[-1]
1248 else:
1249 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001250 ncharsdeleted = 0
1251 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001252 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001253 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001254 chars = chars[:-1]
1255 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001256 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001257 if have <= want or chars[-1] not in " \t":
1258 break
1259 text.undo_block_start()
1260 text.delete("insert-%dc" % ncharsdeleted, "insert")
1261 if have < want:
1262 text.insert("insert", ' ' * (want - have))
1263 text.undo_block_stop()
1264 return "break"
1265
1266 def smart_indent_event(self, event):
1267 # if intraline selection:
1268 # delete it
1269 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001270 # do indent-region
1271 # else:
1272 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001273 text = self.text
1274 first, last = self.get_selection_indices()
1275 text.undo_block_start()
1276 try:
1277 if first and last:
1278 if index2line(first) != index2line(last):
1279 return self.indent_region_event(event)
1280 text.delete(first, last)
1281 text.mark_set("insert", first)
1282 prefix = text.get("insert linestart", "insert")
1283 raw, effective = classifyws(prefix, self.tabwidth)
1284 if raw == len(prefix):
1285 # only whitespace to the left
1286 self.reindent_to(effective + self.indentwidth)
1287 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001288 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001289 if self.usetabs:
1290 pad = '\t'
1291 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001292 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001293 n = self.indentwidth
1294 pad = ' ' * (n - effective % n)
1295 text.insert("insert", pad)
1296 text.see("insert")
1297 return "break"
1298 finally:
1299 text.undo_block_stop()
1300
1301 def newline_and_indent_event(self, event):
1302 text = self.text
1303 first, last = self.get_selection_indices()
1304 text.undo_block_start()
1305 try:
1306 if first and last:
1307 text.delete(first, last)
1308 text.mark_set("insert", first)
1309 line = text.get("insert linestart", "insert")
1310 i, n = 0, len(line)
1311 while i < n and line[i] in " \t":
1312 i = i+1
1313 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001314 # the cursor is in or at leading indentation in a continuation
1315 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001316 text.insert("insert linestart", '\n')
1317 return "break"
1318 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001319 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001320 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001321 last_line_of_prompt = sys.ps1.split('\n')[-1]
1322 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001323 line = line[:-1]
1324 i = i+1
1325 if i:
1326 text.delete("insert - %d chars" % i, "insert")
1327 # strip whitespace after insert point
1328 while text.get("insert") in " \t":
1329 text.delete("insert")
1330 # start new line
1331 text.insert("insert", '\n')
1332
1333 # adjust indentation for continuations and block
1334 # open/close first need to find the last stmt
1335 lno = index2line(text.index('insert'))
1336 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001337 if not self.context_use_ps1:
1338 for context in self.num_context_lines:
1339 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001340 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001341 rawtext = text.get(startatindex, "insert")
1342 y.set_str(rawtext)
1343 bod = y.find_good_parse_start(
1344 self.context_use_ps1,
1345 self._build_char_in_string_func(startatindex))
1346 if bod is not None or startat == 1:
1347 break
1348 y.set_lo(bod or 0)
1349 else:
1350 r = text.tag_prevrange("console", "insert")
1351 if r:
1352 startatindex = r[1]
1353 else:
1354 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001355 rawtext = text.get(startatindex, "insert")
1356 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001357 y.set_lo(0)
1358
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001359 c = y.get_continuation_type()
1360 if c != PyParse.C_NONE:
1361 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001362 if c == PyParse.C_STRING_FIRST_LINE:
1363 # after the first line of a string; do not indent at all
1364 pass
1365 elif c == PyParse.C_STRING_NEXT_LINES:
1366 # inside a string which started before this line;
1367 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001368 text.insert("insert", indent)
1369 elif c == PyParse.C_BRACKET:
1370 # line up with the first (if any) element of the
1371 # last open bracket structure; else indent one
1372 # level beyond the indent of the line with the
1373 # last open bracket
1374 self.reindent_to(y.compute_bracket_indent())
1375 elif c == PyParse.C_BACKSLASH:
1376 # if more than one line in this stmt already, just
1377 # mimic the current indent; else if initial line
1378 # has a start on an assignment stmt, indent to
1379 # beyond leftmost =; else to beyond first chunk of
1380 # non-whitespace on initial line
1381 if y.get_num_lines_in_stmt() > 1:
1382 text.insert("insert", indent)
1383 else:
1384 self.reindent_to(y.compute_backslash_indent())
1385 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001386 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001387 return "break"
1388
1389 # This line starts a brand new stmt; indent relative to
1390 # indentation of initial line of closest preceding
1391 # interesting stmt.
1392 indent = y.get_base_indent_string()
1393 text.insert("insert", indent)
1394 if y.is_block_opener():
1395 self.smart_indent_event(event)
1396 elif indent and y.is_block_closer():
1397 self.smart_backspace_event(event)
1398 return "break"
1399 finally:
1400 text.see("insert")
1401 text.undo_block_stop()
1402
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001403 # Our editwin provides a is_char_in_string function that works
1404 # with a Tk text index, but PyParse only knows about offsets into
1405 # a string. This builds a function for PyParse that accepts an
1406 # offset.
1407
1408 def _build_char_in_string_func(self, startindex):
1409 def inner(offset, _startindex=startindex,
1410 _icis=self.is_char_in_string):
1411 return _icis(_startindex + "+%dc" % offset)
1412 return inner
1413
1414 def indent_region_event(self, event):
1415 head, tail, chars, lines = self.get_region()
1416 for pos in range(len(lines)):
1417 line = lines[pos]
1418 if line:
1419 raw, effective = classifyws(line, self.tabwidth)
1420 effective = effective + self.indentwidth
1421 lines[pos] = self._make_blanks(effective) + line[raw:]
1422 self.set_region(head, tail, chars, lines)
1423 return "break"
1424
1425 def dedent_region_event(self, event):
1426 head, tail, chars, lines = self.get_region()
1427 for pos in range(len(lines)):
1428 line = lines[pos]
1429 if line:
1430 raw, effective = classifyws(line, self.tabwidth)
1431 effective = max(effective - self.indentwidth, 0)
1432 lines[pos] = self._make_blanks(effective) + line[raw:]
1433 self.set_region(head, tail, chars, lines)
1434 return "break"
1435
1436 def comment_region_event(self, event):
1437 head, tail, chars, lines = self.get_region()
1438 for pos in range(len(lines) - 1):
1439 line = lines[pos]
1440 lines[pos] = '##' + line
1441 self.set_region(head, tail, chars, lines)
1442
1443 def uncomment_region_event(self, event):
1444 head, tail, chars, lines = self.get_region()
1445 for pos in range(len(lines)):
1446 line = lines[pos]
1447 if not line:
1448 continue
1449 if line[:2] == '##':
1450 line = line[2:]
1451 elif line[:1] == '#':
1452 line = line[1:]
1453 lines[pos] = line
1454 self.set_region(head, tail, chars, lines)
1455
1456 def tabify_region_event(self, event):
1457 head, tail, chars, lines = self.get_region()
1458 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001459 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001460 for pos in range(len(lines)):
1461 line = lines[pos]
1462 if line:
1463 raw, effective = classifyws(line, tabwidth)
1464 ntabs, nspaces = divmod(effective, tabwidth)
1465 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1466 self.set_region(head, tail, chars, lines)
1467
1468 def untabify_region_event(self, event):
1469 head, tail, chars, lines = self.get_region()
1470 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001471 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001472 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001473 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001474 self.set_region(head, tail, chars, lines)
1475
1476 def toggle_tabs_event(self, event):
1477 if self.askyesno(
1478 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001479 "Turn tabs " + ("on", "off")[self.usetabs] +
1480 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001481 ("will be", "remains at")[self.usetabs] + " 8." +
1482 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001483 parent=self.text):
1484 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001485 # Try to prevent inconsistent indentation.
1486 # User must change indent width manually after using tabs.
1487 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001488 return "break"
1489
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001490 # XXX this isn't bound to anything -- see tabwidth comments
1491## def change_tabwidth_event(self, event):
1492## new = self._asktabwidth()
1493## if new != self.tabwidth:
1494## self.tabwidth = new
1495## self.set_indentation_params(0, guess=0)
1496## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001497
1498 def change_indentwidth_event(self, event):
1499 new = self.askinteger(
1500 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001501 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001502 parent=self.text,
1503 initialvalue=self.indentwidth,
1504 minvalue=2,
1505 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001506 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001507 self.indentwidth = new
1508 return "break"
1509
1510 def get_region(self):
1511 text = self.text
1512 first, last = self.get_selection_indices()
1513 if first and last:
1514 head = text.index(first + " linestart")
1515 tail = text.index(last + "-1c lineend +1c")
1516 else:
1517 head = text.index("insert linestart")
1518 tail = text.index("insert lineend +1c")
1519 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001520 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001521 return head, tail, chars, lines
1522
1523 def set_region(self, head, tail, chars, lines):
1524 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001525 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001526 if newchars == chars:
1527 text.bell()
1528 return
1529 text.tag_remove("sel", "1.0", "end")
1530 text.mark_set("insert", head)
1531 text.undo_block_start()
1532 text.delete(head, tail)
1533 text.insert(head, newchars)
1534 text.undo_block_stop()
1535 text.tag_add("sel", head, "insert")
1536
1537 # Make string that displays as n leading blanks.
1538
1539 def _make_blanks(self, n):
1540 if self.usetabs:
1541 ntabs, nspaces = divmod(n, self.tabwidth)
1542 return '\t' * ntabs + ' ' * nspaces
1543 else:
1544 return ' ' * n
1545
1546 # Delete from beginning of line to insert point, then reinsert
1547 # column logical (meaning use tabs if appropriate) spaces.
1548
1549 def reindent_to(self, column):
1550 text = self.text
1551 text.undo_block_start()
1552 if text.compare("insert linestart", "!=", "insert"):
1553 text.delete("insert linestart", "insert")
1554 if column:
1555 text.insert("insert", self._make_blanks(column))
1556 text.undo_block_stop()
1557
1558 def _asktabwidth(self):
1559 return self.askinteger(
1560 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001561 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001562 parent=self.text,
1563 initialvalue=self.indentwidth,
1564 minvalue=2,
Roger Serwy75b249c2013-04-06 20:26:53 -05001565 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001566
1567 # Guess indentwidth from text content.
1568 # Return guessed indentwidth. This should not be believed unless
1569 # it's in a reasonable range (e.g., it will be 0 if no indented
1570 # blocks are found).
1571
1572 def guess_indent(self):
1573 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1574 if opener and indented:
1575 raw, indentsmall = classifyws(opener, self.tabwidth)
1576 raw, indentlarge = classifyws(indented, self.tabwidth)
1577 else:
1578 indentsmall = indentlarge = 0
1579 return indentlarge - indentsmall
1580
1581# "line.col" -> line, as an int
1582def index2line(index):
1583 return int(float(index))
1584
1585# Look at the leading whitespace in s.
1586# Return pair (# of leading ws characters,
1587# effective # of leading blanks after expanding
1588# tabs to width tabwidth)
1589
1590def classifyws(s, tabwidth):
1591 raw = effective = 0
1592 for ch in s:
1593 if ch == ' ':
1594 raw = raw + 1
1595 effective = effective + 1
1596 elif ch == '\t':
1597 raw = raw + 1
1598 effective = (effective // tabwidth + 1) * tabwidth
1599 else:
1600 break
1601 return raw, effective
1602
1603import tokenize
1604_tokenize = tokenize
1605del tokenize
1606
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001607class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001608
1609 # .run() chews over the Text widget, looking for a block opener
1610 # and the stmt following it. Returns a pair,
1611 # (line containing block opener, line containing stmt)
1612 # Either or both may be None.
1613
1614 def __init__(self, text, tabwidth):
1615 self.text = text
1616 self.tabwidth = tabwidth
1617 self.i = self.finished = 0
1618 self.blkopenline = self.indentedline = None
1619
1620 def readline(self):
1621 if self.finished:
1622 return ""
1623 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001624 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001625 if self.text.compare(mark, ">=", "end"):
1626 return ""
1627 return self.text.get(mark, mark + " lineend+1c")
1628
1629 def tokeneater(self, type, token, start, end, line,
1630 INDENT=_tokenize.INDENT,
1631 NAME=_tokenize.NAME,
1632 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1633 if self.finished:
1634 pass
1635 elif type == NAME and token in OPENERS:
1636 self.blkopenline = line
1637 elif type == INDENT and self.blkopenline:
1638 self.indentedline = line
1639 self.finished = 1
1640
1641 def run(self):
1642 save_tabsize = _tokenize.tabsize
1643 _tokenize.tabsize = self.tabwidth
1644 try:
1645 try:
1646 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001647 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001648 # since we cut off the tokenizer early, we can trigger
1649 # spurious errors
1650 pass
1651 finally:
1652 _tokenize.tabsize = save_tabsize
1653 return self.blkopenline, self.indentedline
1654
1655### end autoindent code ###
1656
David Scherer7aced172000-08-15 01:13:23 +00001657def prepstr(s):
1658 # Helper to extract the underscore from a string, e.g.
1659 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001660 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001661 if i >= 0:
1662 s = s[:i] + s[i+1:]
1663 return i, s
1664
1665
1666keynames = {
1667 'bracketleft': '[',
1668 'bracketright': ']',
1669 'slash': '/',
1670}
1671
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001672def get_accelerator(keydefs, eventname):
1673 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001674 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1675 # if not keylist:
Ned Deily57847df2014-03-27 20:47:04 -07001676 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily60651532011-01-31 00:52:49 +00001677 "<<open-module>>",
1678 "<<goto-line>>",
1679 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001680 return ""
1681 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001682 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001683 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1684 s = re.sub("Key-", "", s)
1685 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1686 s = re.sub("Control-", "Ctrl-", s)
1687 s = re.sub("-", "+", s)
1688 s = re.sub("><", " ", s)
1689 s = re.sub("<", "", s)
1690 s = re.sub(">", "", s)
1691 return s
1692
1693
1694def fixwordbreaks(root):
1695 # Make sure that Tk's double-click and next/previous word
1696 # operations use our definition of a word (i.e. an identifier)
1697 tk = root.tk
1698 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1699 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1700 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1701
1702
Terry Jan Reedycf834762014-10-17 01:31:29 -04001703def _editor_window(parent): # htest #
1704 # error if close master window first - timer event, after script
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001705 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001706 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001707 if sys.argv[1:]:
1708 filename = sys.argv[1]
1709 else:
1710 filename = None
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001711 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001712 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001713 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycf834762014-10-17 01:31:29 -04001714 # Does not stop error, neither does following
1715 # edit.text.bind("<<close-window>>", edit.close_event)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001716
David Scherer7aced172000-08-15 01:13:23 +00001717
1718if __name__ == '__main__':
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001719 from idlelib.idle_test.htest import run
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001720 run(_help_dialog, _editor_window)