blob: 8268161512fc05e2326b34f5566301211af0b718 [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):
Terry Jan Reedy70e763c2015-09-20 19:55:44 -040075 import warnings as w
76 w.warn("EditorWindow.HelpDialog is no longer used by Idle.\n"
77 "It will be removed in 3.6 or later.\n"
78 "It has been replaced by private help.HelpWindow\n",
79 DeprecationWarning, stacklevel=2)
Terry Jan Reedyadb87e22012-02-05 15:10:55 -050080 self.parent = None # parent of help window
81 self.dlg = None # the help window iteself
82
83 def display(self, parent, near=None):
84 """ Display the help dialog.
85
86 parent - parent widget for the help window
87
88 near - a Toplevel widget (e.g. EditorWindow or PyShell)
89 to use as a reference for placing the help window
90 """
91 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
117helpDialog = HelpDialog() # singleton instance
Terry Jan Reedy43458462014-05-19 00:12:00 -0400118def _help_dialog(parent): # wrapper for htest
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -0400119 helpDialog.show_dialog(parent)
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500120
121
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000122class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +0000123 from idlelib.Percolator import Percolator
124 from idlelib.ColorDelegator import ColorDelegator
125 from idlelib.UndoDelegator import UndoDelegator
126 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
127 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +0000128 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +0000129 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000130
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000131 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000132
133 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000134 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +0000135 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000136 if sys.platform.count('linux'):
137 # look for html docs in a couple of standard places
138 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
139 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
140 dochome = '/var/www/html/python/index.html'
141 else:
142 basepath = '/usr/share/doc/' # standard location
143 dochome = os.path.join(basepath, pyver,
144 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000145 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +0000146 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +0000147 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000148 if os.path.isfile(chmfile):
149 dochome = chmfile
Ned Deily57847df2014-03-27 20:47:04 -0700150 elif sys.platform == 'darwin':
151 # documentation may be stored inside a python framework
Ronald Oussoren19302d92006-06-11 14:33:36 +0000152 dochome = os.path.join(sys.prefix,
153 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000154 dochome = os.path.normpath(dochome)
155 if os.path.isfile(dochome):
156 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +0000157 if sys.platform == 'darwin':
158 # Safari requires real file:-URLs
159 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000160 else:
Terry Jan Reedyb17c1e02014-09-19 22:54:09 -0400161 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
David Scherer7aced172000-08-15 01:13:23 +0000162 self.flist = flist
163 root = root or flist.root
164 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +0000165 try:
166 sys.ps1
167 except AttributeError:
168 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000169 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000170 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000171 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000172 self.tkinter_vars = flist.vars
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200173 #self.top.instance_dict makes flist.inversedict available to
174 #configDialog.py so it can access all EditorWindow instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000175 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000176 else:
177 self.tkinter_vars = {} # keys: Tkinter event names
178 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000179 self.top.instance_dict = {}
180 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000181 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000182 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000183 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200184 self.width = idleConf.GetOption('main','EditorWindow','width', type='int')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000185 text_options = {
186 'name': 'text',
187 'padx': 5,
188 'wrap': 'none',
189 'width': self.width,
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200190 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')}
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000191 if TkVersion >= 8.5:
192 # Starting with tk 8.5 we have to set the new tabstyle option
193 # to 'wordprocessor' to achieve the same display of tabs as in
194 # older tk versions.
195 text_options['tabstyle'] = 'wordprocessor'
196 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000197 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000198
199 self.createmenubar()
200 self.apply_bindings()
201
202 self.top.protocol("WM_DELETE_WINDOW", self.close)
203 self.top.bind("<<close-window>>", self.close_event)
Ned Deily57847df2014-03-27 20:47:04 -0700204 if macosxSupport.isAquaTk():
Ronald Oussoren17db4952006-07-23 09:41:09 +0000205 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000206 text.bind('<<close-window>>', self.close_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000207 # Some OS X systems have only one mouse button,
208 # so use control-click for pulldown menus there.
209 # (Note, AquaTk defines <2> as the right button if
210 # present and the Tk Text widget already binds <2>.)
211 text.bind("<Control-Button-1>",self.right_menu_event)
212 else:
213 # Elsewhere, use right-click for pulldown menus.
214 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000215 text.bind("<<cut>>", self.cut)
216 text.bind("<<copy>>", self.copy)
217 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000218 text.bind("<<center-insert>>", self.center_insert_event)
219 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000220 text.bind("<<python-docs>>", self.python_docs)
221 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000222 text.bind("<<open-config-dialog>>", self.config_dialog)
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400223 text.bind("<<open-config-extensions-dialog>>",
224 self.config_extensions_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000225 text.bind("<<open-module>>", self.open_module)
226 text.bind("<<do-nothing>>", lambda event: "break")
227 text.bind("<<select-all>>", self.select_all)
228 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000229 text.bind("<<find>>", self.find_event)
230 text.bind("<<find-again>>", self.find_again_event)
231 text.bind("<<find-in-files>>", self.find_in_files_event)
232 text.bind("<<find-selection>>", self.find_selection_event)
233 text.bind("<<replace>>", self.replace_event)
234 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000235 text.bind("<<smart-backspace>>",self.smart_backspace_event)
236 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
237 text.bind("<<smart-indent>>",self.smart_indent_event)
238 text.bind("<<indent-region>>",self.indent_region_event)
239 text.bind("<<dedent-region>>",self.dedent_region_event)
240 text.bind("<<comment-region>>",self.comment_region_event)
241 text.bind("<<uncomment-region>>",self.uncomment_region_event)
242 text.bind("<<tabify-region>>",self.tabify_region_event)
243 text.bind("<<untabify-region>>",self.untabify_region_event)
244 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
245 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000246 text.bind("<Left>", self.move_at_edge_if_selection(0))
247 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000248 text.bind("<<del-word-left>>", self.del_word_left)
249 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000250 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000251
David Scherer7aced172000-08-15 01:13:23 +0000252 if flist:
253 flist.inversedict[self] = key
254 if key:
255 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000256 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000257 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
258 text.bind("<<open-class-browser>>", self.open_class_browser)
259 text.bind("<<open-path-browser>>", self.open_path_browser)
260
Steven M. Gava898a3652001-10-07 11:10:44 +0000261 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000262 vbar['command'] = text.yview
263 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000264 text['yscrollcommand'] = vbar.set
Terry Jan Reedy12352142015-08-01 18:57:27 -0400265 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000266 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
267 text.pack(side=TOP, fill=BOTH, expand=1)
268 text.focus_set()
269
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000270 # usetabs true -> literal tab characters are used by indent and
271 # dedent cmds, possibly mixed with spaces if
272 # indentwidth is not a multiple of tabwidth,
273 # which will cause Tabnanny to nag!
274 # false -> tab characters are converted to spaces by indent
275 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000276 # Although use-spaces=0 can be configured manually in config-main.def,
277 # configuration of tabs v. spaces is not supported in the configuration
278 # dialog. IDLE promotes the preferred Python indentation: use spaces!
279 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
280 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000281
282 # tabwidth is the display width of a literal tab character.
283 # CAUTION: telling Tk to use anything other than its default
284 # tab setting causes it to use an entirely different tabbing algorithm,
285 # treating tab stops as fixed distances from the left margin.
286 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000287 self.tabwidth = 8 # must remain 8 until Tk is fixed.
288
289 # indentwidth is the number of screen characters per indent level.
290 # The recommended Python indentation is four spaces.
291 self.indentwidth = self.tabwidth
292 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000293
294 # If context_use_ps1 is true, parsing searches back for a ps1 line;
295 # else searches for a popular (if, def, ...) Python stmt.
296 self.context_use_ps1 = False
297
298 # When searching backwards for a reliable place to begin parsing,
299 # first start num_context_lines[0] lines back, then
300 # num_context_lines[1] lines back if that didn't work, and so on.
301 # The last value should be huge (larger than the # of lines in a
302 # conceivable file).
303 # Making the initial values larger slows things down more often.
304 self.num_context_lines = 50, 500, 5000000
305
David Scherer7aced172000-08-15 01:13:23 +0000306 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000307
308 self.undo = undo = self.UndoDelegator()
309 per.insertfilter(undo)
310 text.undo_block_start = undo.undo_block_start
311 text.undo_block_stop = undo.undo_block_stop
312 undo.set_saved_change_hook(self.saved_change_hook)
313
314 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000315 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000316 io.set_filename_change_hook(self.filename_change_hook)
317
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000318 # Create the recent files submenu
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400319 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000320 self.menudict['file'].insert_cascade(3, label='Recent Files',
321 underline=0,
322 menu=self.recent_files_menu)
323 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000324
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000325 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000326 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000327 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000328 io.loadfile(filename)
329 else:
330 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000331 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000332 self.saved_change_hook()
333
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000334 self.set_indentation_params(self.ispythonsource(filename))
335
David Scherer7aced172000-08-15 01:13:23 +0000336 self.load_extensions()
337
338 menu = self.menudict.get('windows')
339 if menu:
340 end = menu.index("end")
341 if end is None:
342 end = -1
343 if end >= 0:
344 menu.add_separator()
345 end = end + 1
346 self.wmenu_end = end
347 WindowList.register_callback(self.postwindowsmenu)
348
349 # Some abstractions so IDLE extensions are cross-IDE
350 self.askyesno = tkMessageBox.askyesno
351 self.askinteger = tkSimpleDialog.askinteger
352 self.showerror = tkMessageBox.showerror
353
Roger Serwy02c0ed02013-05-20 22:13:39 -0500354 self._highlight_workaround() # Fix selection tags on Windows
355
356 def _highlight_workaround(self):
357 # On Windows, Tk removes painting of the selection
358 # tags which is different behavior than on Linux and Mac.
359 # See issue14146 for more information.
360 if not sys.platform.startswith('win'):
361 return
362
363 text = self.text
364 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
365 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
366 def highlight_fix(focus):
367 sel_range = text.tag_ranges("sel")
368 if sel_range:
369 if focus == 'out':
370 HILITE_CONFIG = idleConf.GetHighlight(
371 idleConf.CurrentTheme(), 'hilite')
372 text.tag_config("sel_fix", HILITE_CONFIG)
373 text.tag_raise("sel_fix")
374 text.tag_add("sel_fix", *sel_range)
375 elif focus == 'in':
376 text.tag_remove("sel_fix", "1.0", "end")
377
378 text.bind("<<Highlight-FocusOut>>",
379 lambda ev: highlight_fix("out"))
380 text.bind("<<Highlight-FocusIn>>",
381 lambda ev: highlight_fix("in"))
382
383
Martin v. Löwis307021f2005-11-27 16:59:04 +0000384 def _filename_to_unicode(self, filename):
385 """convert filename to unicode in order to display it in Tk"""
386 if isinstance(filename, unicode) or not filename:
387 return filename
388 else:
389 try:
390 return filename.decode(self.filesystemencoding)
391 except UnicodeDecodeError:
392 # XXX
393 try:
394 return filename.decode(self.encoding)
395 except UnicodeDecodeError:
396 # byte-to-byte conversion
397 return filename.decode('iso8859-1')
398
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000399 def new_callback(self, event):
400 dirname, basename = self.io.defaultfilename()
401 self.flist.new(dirname)
402 return "break"
403
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000404 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400405 if (event.state & 4) != 0 and event.keysym == "Home":
406 # state&4==Control. If <Control-Home>, use the Tk binding.
407 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000408 if self.text.index("iomark") and \
409 self.text.compare("iomark", "<=", "insert lineend") and \
410 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400411 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000412 insertpt = int(self.text.index("iomark").split(".")[1])
413 else:
414 line = self.text.get("insert linestart", "insert lineend")
415 for insertpt in xrange(len(line)):
416 if line[insertpt] not in (' ','\t'):
417 break
418 else:
419 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000420 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000421 if insertpt == lineat:
422 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000423 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000424 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400425 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000426 self.text.tag_remove("sel", "1.0", "end")
427 else:
428 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400429 self.text.mark_set("my_anchor", "insert") # there was no previous selection
430 else:
431 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
432 self.text.mark_set("my_anchor", "sel.first") # extend back
433 else:
434 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000435 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400436 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000437 if self.text.compare(first,">",last):
438 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000439 self.text.tag_remove("sel", "1.0", "end")
440 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000441 self.text.mark_set("insert", dest)
442 self.text.see("insert")
443 return "break"
444
David Scherer7aced172000-08-15 01:13:23 +0000445 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000446 self.status_bar = self.MultiStatusBar(self.top)
Ned Deily57847df2014-03-27 20:47:04 -0700447 if sys.platform == "darwin":
Ronald Oussoren19302d92006-06-11 14:33:36 +0000448 # Insert some padding to avoid obscuring some of the statusbar
449 # by the resize widget.
450 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000451 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
452 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
453 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000454 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
455 self.text.event_add("<<set-line-and-column>>",
456 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000457 self.text.after_idle(self.set_line_and_column)
458
459 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000460 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000461 self.status_bar.set_label('column', 'Col: %s' % column)
462 self.status_bar.set_label('line', 'Ln: %s' % line)
463
David Scherer7aced172000-08-15 01:13:23 +0000464 menu_specs = [
465 ("file", "_File"),
466 ("edit", "_Edit"),
467 ("format", "F_ormat"),
468 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000469 ("options", "_Options"),
Ned Deilyb5daa3d2015-01-17 21:03:41 -0800470 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000471 ("help", "_Help"),
472 ]
473
Ronald Oussoren19302d92006-06-11 14:33:36 +0000474
David Scherer7aced172000-08-15 01:13:23 +0000475 def createmenubar(self):
476 mbar = self.menubar
477 self.menudict = menudict = {}
478 for name, label in self.menu_specs:
479 underline, label = prepstr(label)
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400480 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000481 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000482
Ned Deily57847df2014-03-27 20:47:04 -0700483 if macosxSupport.isCarbonTk():
Ronald Oussoren19302d92006-06-11 14:33:36 +0000484 # Insert the application menu
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400485 menudict['application'] = menu = Menu(mbar, name='apple',
486 tearoff=0)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000487 mbar.add_cascade(label='IDLE', menu=menu)
488
David Scherer7aced172000-08-15 01:13:23 +0000489 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000490 self.base_helpmenu_length = self.menudict['help'].index(END)
491 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000492
493 def postwindowsmenu(self):
494 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000495 menu = self.menudict['windows']
496 end = menu.index("end")
497 if end is None:
498 end = -1
499 if end > self.wmenu_end:
500 menu.delete(self.wmenu_end+1, end)
501 WindowList.add_windows_to_menu(menu)
502
503 rmenu = None
504
505 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000506 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
507 if not self.rmenu:
508 self.make_rmenu()
509 rmenu = self.rmenu
510 self.event = event
511 iswin = sys.platform[:3] == 'win'
512 if iswin:
513 self.text.config(cursor="arrow")
Andrew Svetlov5018db72012-11-01 22:39:14 +0200514
Roger Serwy231a8fd2013-04-07 12:15:52 -0500515 for item in self.rmenu_specs:
516 try:
517 label, eventname, verify_state = item
518 except ValueError: # see issue1207589
519 continue
520
Andrew Svetlov5018db72012-11-01 22:39:14 +0200521 if verify_state is None:
522 continue
523 state = getattr(self, verify_state)()
524 rmenu.entryconfigure(label, state=state)
525
David Scherer7aced172000-08-15 01:13:23 +0000526 rmenu.tk_popup(event.x_root, event.y_root)
527 if iswin:
528 self.text.config(cursor="ibeam")
529
530 rmenu_specs = [
Andrew Svetlov5018db72012-11-01 22:39:14 +0200531 # ("Label", "<<virtual-event>>", "statefuncname"), ...
532 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000533 ]
534
535 def make_rmenu(self):
536 rmenu = Menu(self.text, tearoff=0)
Roger Serwy231a8fd2013-04-07 12:15:52 -0500537 for item in self.rmenu_specs:
538 label, eventname = item[0], item[1]
Andrew Svetlov5018db72012-11-01 22:39:14 +0200539 if label is not None:
540 def command(text=self.text, eventname=eventname):
541 text.event_generate(eventname)
542 rmenu.add_command(label=label, command=command)
543 else:
544 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000545 self.rmenu = rmenu
546
Andrew Svetlov5018db72012-11-01 22:39:14 +0200547 def rmenu_check_cut(self):
548 return self.rmenu_check_copy()
549
550 def rmenu_check_copy(self):
551 try:
552 indx = self.text.index('sel.first')
553 except TclError:
554 return 'disabled'
555 else:
556 return 'normal' if indx else 'disabled'
557
558 def rmenu_check_paste(self):
559 try:
560 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
561 except TclError:
562 return 'disabled'
563 else:
564 return 'normal'
565
David Scherer7aced172000-08-15 01:13:23 +0000566 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000567 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000568
Steven M. Gava3b55a892001-11-21 05:56:26 +0000569 def config_dialog(self, event=None):
570 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400571 def config_extensions_dialog(self, event=None):
572 configDialog.ConfigExtensionsDialog(self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000573
David Scherer7aced172000-08-15 01:13:23 +0000574 def help_dialog(self, event=None):
Terry Jan Reedy70e763c2015-09-20 19:55:44 -0400575 "Handle help doc event."
576 # edit maxosxSupport.overrideRootMenu.help_dialog to match
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500577 if self.root:
578 parent = self.root
579 else:
580 parent = self.top
Terry Jan Reedy70e763c2015-09-20 19:55:44 -0400581 help.show_idlehelp(parent)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000582
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000583 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000584 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000585 try:
586 os.startfile(self.help_url)
587 except WindowsError as why:
588 tkMessageBox.showerror(title='Document Start Failure',
589 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000590 else:
591 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000592 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000593
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000594 def cut(self,event):
595 self.text.event_generate("<<Cut>>")
596 return "break"
597
598 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000599 if not self.text.tag_ranges("sel"):
600 # There is no selection, so do nothing and maybe interrupt.
601 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000602 self.text.event_generate("<<Copy>>")
603 return "break"
604
605 def paste(self,event):
606 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000607 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000608 return "break"
609
David Scherer7aced172000-08-15 01:13:23 +0000610 def select_all(self, event=None):
611 self.text.tag_add("sel", "1.0", "end-1c")
612 self.text.mark_set("insert", "1.0")
613 self.text.see("insert")
614 return "break"
615
616 def remove_selection(self, event=None):
617 self.text.tag_remove("sel", "1.0", "end")
618 self.text.see("insert")
619
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000620 def move_at_edge_if_selection(self, edge_index):
621 """Cursor move begins at start or end of selection
622
623 When a left/right cursor key is pressed create and return to Tkinter a
624 function which causes a cursor move from the associated edge of the
625 selection.
626
627 """
628 self_text_index = self.text.index
629 self_text_mark_set = self.text.mark_set
630 edges_table = ("sel.first+1c", "sel.last-1c")
631 def move_at_edge(event):
632 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
633 try:
634 self_text_index("sel.first")
635 self_text_mark_set("insert", edges_table[edge_index])
636 except TclError:
637 pass
638 return move_at_edge
639
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000640 def del_word_left(self, event):
641 self.text.event_generate('<Meta-Delete>')
642 return "break"
643
644 def del_word_right(self, event):
645 self.text.event_generate('<Meta-d>')
646 return "break"
647
Steven M. Gavac5976402002-01-04 03:06:08 +0000648 def find_event(self, event):
649 SearchDialog.find(self.text)
650 return "break"
651
652 def find_again_event(self, event):
653 SearchDialog.find_again(self.text)
654 return "break"
655
656 def find_selection_event(self, event):
657 SearchDialog.find_selection(self.text)
658 return "break"
659
660 def find_in_files_event(self, event):
661 GrepDialog.grep(self.text, self.io, self.flist)
662 return "break"
663
664 def replace_event(self, event):
665 ReplaceDialog.replace(self.text)
666 return "break"
667
668 def goto_line_event(self, event):
669 text = self.text
670 lineno = tkSimpleDialog.askinteger("Goto",
671 "Go to line number:",parent=text)
672 if lineno is None:
673 return "break"
674 if lineno <= 0:
675 text.bell()
676 return "break"
677 text.mark_set("insert", "%d.0" % lineno)
678 text.see("insert")
679
David Scherer7aced172000-08-15 01:13:23 +0000680 def open_module(self, event=None):
681 # XXX Shouldn't this be in IOBinding or in FileList?
682 try:
683 name = self.text.get("sel.first", "sel.last")
684 except TclError:
685 name = ""
686 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000687 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000688 name = tkSimpleDialog.askstring("Module",
689 "Enter the name of a Python module\n"
690 "to search on sys.path and open:",
691 parent=self.text, initialvalue=name)
692 if name:
693 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000694 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000695 return
David Scherer7aced172000-08-15 01:13:23 +0000696 # XXX Ought to insert current file's directory in front of path
697 try:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400698 (f, file_path, (suffix, mode, mtype)) = _find_module(name)
Terry Jan Reedy2b149862013-06-29 00:59:34 -0400699 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000700 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
701 return
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400702 if mtype != imp.PY_SOURCE:
David Scherer7aced172000-08-15 01:13:23 +0000703 tkMessageBox.showerror("Unsupported type",
704 "%s is not a source module" % name, parent=self.text)
705 return
706 if f:
707 f.close()
708 if self.flist:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400709 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000710 else:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400711 self.io.loadfile(file_path)
712 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000713
714 def open_class_browser(self, event=None):
715 filename = self.io.filename
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400716 if not (self.__class__.__name__ == 'PyShellEditorWindow'
717 and filename):
718 filename = self.open_module()
719 if filename is None:
720 return
David Scherer7aced172000-08-15 01:13:23 +0000721 head, tail = os.path.split(filename)
722 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000723 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000724 ClassBrowser.ClassBrowser(self.flist, base, [head])
725
726 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000727 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000728 PathBrowser.PathBrowser(self.flist)
729
730 def gotoline(self, lineno):
731 if lineno is not None and lineno > 0:
732 self.text.mark_set("insert", "%d.0" % lineno)
733 self.text.tag_remove("sel", "1.0", "end")
734 self.text.tag_add("sel", "insert", "insert +1l")
735 self.center()
736
737 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000738 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000739 return True
David Scherer7aced172000-08-15 01:13:23 +0000740 base, ext = os.path.splitext(os.path.basename(filename))
741 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000742 return True
David Scherer7aced172000-08-15 01:13:23 +0000743 try:
744 f = open(filename)
745 line = f.readline()
746 f.close()
747 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000748 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000749 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000750
751 def close_hook(self):
752 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000753 self.flist.unregister_maybe_terminate(self)
754 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000755
756 def set_close_hook(self, close_hook):
757 self.close_hook = close_hook
758
759 def filename_change_hook(self):
760 if self.flist:
761 self.flist.filename_changed_edit(self)
762 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000763 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000764 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000765
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000766 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000767 if self.color:
768 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000769 if self.ispythonsource(self.io.filename):
770 self.color = self.ColorDelegator()
771 # can add more colorizers here...
772 if self.color:
773 self.per.removefilter(self.undo)
774 self.per.insertfilter(self.color)
775 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000776
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000777 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000778 if not self.color:
779 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000780 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000781 self.per.removefilter(self.color)
782 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000783
Steven M. Gavab77d3432002-03-02 07:16:21 +0000784 def ResetColorizer(self):
Terry Jan Reedy13755382014-10-09 18:44:26 -0400785 "Update the color theme"
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000786 # Called from self.filename_change_hook and from configDialog.py
787 self._rmcolorizer()
788 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000789 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000790 normal_colors = idleConf.GetHighlight(theme, 'normal')
791 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
792 select_colors = idleConf.GetHighlight(theme, 'hilite')
793 self.text.config(
794 foreground=normal_colors['foreground'],
795 background=normal_colors['background'],
796 insertbackground=cursor_color,
797 selectforeground=select_colors['foreground'],
798 selectbackground=select_colors['background'],
799 )
David Scherer7aced172000-08-15 01:13:23 +0000800
Steven M. Gavab1585412002-03-12 00:21:56 +0000801 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000802 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000803 # Called from configDialog.py
Terry Jan Reedy12352142015-08-01 18:57:27 -0400804
805 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000806
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000807 def RemoveKeybindings(self):
808 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000809 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000810 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000811 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000812 self.text.event_delete(event, *keylist)
813 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000814 xkeydefs = idleConf.GetExtensionBindings(extensionName)
815 if xkeydefs:
816 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000817 self.text.event_delete(event, *keylist)
818
819 def ApplyKeybindings(self):
820 "Update the keybindings after they are changed"
821 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000822 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000823 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000824 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000825 xkeydefs = idleConf.GetExtensionBindings(extensionName)
826 if xkeydefs:
827 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000828 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000829 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000830 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000831 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000832 for item in menu[1]:
833 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000834 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000835 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000836 menu = self.menudict[menubarItem]
Ned Deily14ef0c82013-07-20 14:38:24 -0700837 end = menu.index(END)
838 if end is None:
839 # Skip empty menus
840 continue
841 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000842 for index in range(0, end):
843 if menu.type(index) == 'command':
844 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000845 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000846 itemName = menu.entrycget(index, 'label')
847 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000848 if menubarItem in menuEventDict:
849 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000850 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000851 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000852 accel = get_accelerator(keydefs, event)
853 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000854
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000855 def set_notabs_indentwidth(self):
856 "Update the indentwidth if changed and not using tabs in this window"
857 # Called from configDialog.py
858 if not self.usetabs:
859 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
860 type='int')
861
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000862 def reset_help_menu_entries(self):
863 "Update the additional help entries on the Help menu"
864 help_list = idleConf.GetAllExtraHelpSourcesList()
865 helpmenu = self.menudict['help']
866 # first delete the extra help entries, if any
867 helpmenu_length = helpmenu.index(END)
868 if helpmenu_length > self.base_helpmenu_length:
869 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
870 # then rebuild them
871 if help_list:
872 helpmenu.add_separator()
873 for entry in help_list:
874 cmd = self.__extra_help_callback(entry[1])
875 helpmenu.add_command(label=entry[0], command=cmd)
876 # and update the menu dictionary
877 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000878
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000879 def __extra_help_callback(self, helpfile):
880 "Create a callback with the helpfile value frozen at definition time"
881 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000882 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000883 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000884 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000885 try:
886 os.startfile(helpfile)
887 except WindowsError as why:
888 tkMessageBox.showerror(title='Document Start Failure',
889 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000890 else:
891 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000892 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000893
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000894 def update_recent_files_list(self, new_file=None):
895 "Load and update the recent files list and menus"
896 rf_list = []
897 if os.path.exists(self.recent_files_path):
Terry Jan Reedyf9489432013-08-04 15:39:56 -0400898 with open(self.recent_files_path, 'r') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000899 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000900 if new_file:
901 new_file = os.path.abspath(new_file) + '\n'
902 if new_file in rf_list:
903 rf_list.remove(new_file) # move to top
904 rf_list.insert(0, new_file)
905 # clean and save the recent files list
906 bad_paths = []
907 for path in rf_list:
908 if '\0' in path or not os.path.exists(path[0:-1]):
909 bad_paths.append(path)
910 rf_list = [path for path in rf_list if path not in bad_paths]
911 ulchars = "1234567890ABCDEFGHIJK"
912 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000913 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800914 with open(self.recent_files_path, 'w') as rf_file:
915 rf_file.writelines(rf_list)
916 except IOError as err:
917 if not getattr(self.root, "recentfilelist_error_displayed", False):
918 self.root.recentfilelist_error_displayed = True
919 tkMessageBox.showerror(title='IDLE Error',
920 message='Unable to update Recent Files list:\n%s'
921 % str(err),
922 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000923 # for each edit window instance, construct the recent files menu
924 for instance in self.top.instance_dict.keys():
925 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700926 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000927 for i, file_name in enumerate(rf_list):
928 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000929 # make unicode string to display non-ASCII chars correctly
930 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000931 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000932 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000933 command=callback,
934 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000935
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000936 def __recent_file_callback(self, file_name):
937 def open_recent_file(fn_closure=file_name):
938 self.io.open(editFile=fn_closure)
939 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000940
David Scherer7aced172000-08-15 01:13:23 +0000941 def saved_change_hook(self):
942 short = self.short_title()
943 long = self.long_title()
944 if short and long:
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400945 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000946 elif short:
947 title = short
948 elif long:
949 title = long
950 else:
951 title = "Untitled"
952 icon = short or long or title
953 if not self.get_saved():
954 title = "*%s*" % title
955 icon = "*%s" % icon
956 self.top.wm_title(title)
957 self.top.wm_iconname(icon)
958
959 def get_saved(self):
960 return self.undo.get_saved()
961
962 def set_saved(self, flag):
963 self.undo.set_saved(flag)
964
965 def reset_undo(self):
966 self.undo.reset_undo()
967
968 def short_title(self):
969 filename = self.io.filename
970 if filename:
971 filename = os.path.basename(filename)
Terry Jan Reedy59243652014-01-23 00:36:37 -0500972 else:
973 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000974 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400975 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000976
977 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000978 # return unicode string to display non-ASCII chars correctly
979 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000980
981 def center_insert_event(self, event):
982 self.center()
983
984 def center(self, mark="insert"):
985 text = self.text
986 top, bot = self.getwindowlines()
987 lineno = self.getlineno(mark)
988 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000989 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000990 text.yview(float(newtop))
991
992 def getwindowlines(self):
993 text = self.text
994 top = self.getlineno("@0,0")
995 bot = self.getlineno("@0,65535")
996 if top == bot and text.winfo_height() == 1:
997 # Geometry manager hasn't run yet
998 height = int(text['height'])
999 bot = top + height - 1
1000 return top, bot
1001
1002 def getlineno(self, mark="insert"):
1003 text = self.text
1004 return int(float(text.index(mark)))
1005
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001006 def get_geometry(self):
1007 "Return (width, height, x, y)"
1008 geom = self.top.wm_geometry()
1009 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
1010 tuple = (map(int, m.groups()))
1011 return tuple
1012
David Scherer7aced172000-08-15 01:13:23 +00001013 def close_event(self, event):
1014 self.close()
1015
1016 def maybesave(self):
1017 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001018 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001019 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001020 self.top.deiconify()
1021 self.top.lower()
1022 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001023 return self.io.maybesave()
1024
1025 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001026 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +00001027 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001028 self._close()
1029 return reply
1030
1031 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001032 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001033 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001034 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001035 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001036 self.io.close()
1037 self.io = None
1038 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001039 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001040 self.color.close(False)
1041 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001042 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001043 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001044 self.per.close()
1045 self.per = None
1046 self.top.destroy()
1047 if self.close_hook:
1048 # unless override: unregister from flist, terminate if last window
1049 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001050
1051 def load_extensions(self):
1052 self.extensions = {}
1053 self.load_standard_extensions()
1054
1055 def unload_extensions(self):
1056 for ins in self.extensions.values():
1057 if hasattr(ins, "close"):
1058 ins.close()
1059 self.extensions = {}
1060
1061 def load_standard_extensions(self):
1062 for name in self.get_standard_extension_names():
1063 try:
1064 self.load_extension(name)
1065 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001066 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001067 import traceback
1068 traceback.print_exc()
1069
1070 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001071 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001072
1073 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001074 try:
1075 mod = __import__(name, globals(), locals(), [])
1076 except ImportError:
1077 print "\nFailed to import extension: ", name
1078 return
David Scherer7aced172000-08-15 01:13:23 +00001079 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001080 keydefs = idleConf.GetExtensionBindings(name)
1081 if hasattr(cls, "menudefs"):
1082 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001083 ins = cls(self)
1084 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001085 if keydefs:
1086 self.apply_bindings(keydefs)
1087 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001088 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001089 while methodname[:1] == '<':
1090 methodname = methodname[1:]
1091 while methodname[-1:] == '>':
1092 methodname = methodname[:-1]
1093 methodname = methodname + "_event"
1094 if hasattr(ins, methodname):
1095 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001096
1097 def apply_bindings(self, keydefs=None):
1098 if keydefs is None:
1099 keydefs = self.Bindings.default_keydefs
1100 text = self.text
1101 text.keydefs = keydefs
1102 for event, keylist in keydefs.items():
1103 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001104 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001105
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001106 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001107 """Add appropriate entries to the menus and submenus
1108
1109 Menus that are absent or None in self.menudict are ignored.
1110 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001111 if menudefs is None:
1112 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001113 if keydefs is None:
1114 keydefs = self.Bindings.default_keydefs
1115 menudict = self.menudict
1116 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001117 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001118 menu = menudict.get(mname)
1119 if not menu:
1120 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001121 for entry in entrylist:
1122 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001123 menu.add_separator()
1124 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001125 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001126 checkbutton = (label[:1] == '!')
1127 if checkbutton:
1128 label = label[1:]
1129 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001130 accelerator = get_accelerator(keydefs, eventname)
1131 def command(text=text, eventname=eventname):
1132 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001133 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001134 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001135 menu.add_checkbutton(label=label, underline=underline,
1136 command=command, accelerator=accelerator,
1137 variable=var)
1138 else:
1139 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001140 command=command,
1141 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001142
1143 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001144 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001145 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001146 value = var.get()
1147 return value
1148 else:
1149 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001150
1151 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001152 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001153 if var:
1154 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001155 else:
1156 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001157
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001158 def get_var_obj(self, name, vartype=None):
1159 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001160 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001161 # create a Tkinter variable object with self.text as master:
1162 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001163 return var
1164
1165 # Tk implementations of "virtual text methods" -- each platform
1166 # reusing IDLE's support code needs to define these for its GUI's
1167 # flavor of widget.
1168
1169 # Is character at text_index in a Python string? Return 0 for
1170 # "guaranteed no", true for anything else. This info is expensive
1171 # to compute ab initio, but is probably already known by the
1172 # platform's colorizer.
1173
1174 def is_char_in_string(self, text_index):
1175 if self.color:
1176 # Return true iff colorizer hasn't (re)gotten this far
1177 # yet, or the character is tagged as being in a string
1178 return self.text.tag_prevrange("TODO", text_index) or \
1179 "STRING" in self.text.tag_names(text_index)
1180 else:
1181 # The colorizer is missing: assume the worst
1182 return 1
1183
1184 # If a selection is defined in the text widget, return (start,
1185 # end) as Tkinter text indices, otherwise return (None, None)
1186 def get_selection_indices(self):
1187 try:
1188 first = self.text.index("sel.first")
1189 last = self.text.index("sel.last")
1190 return first, last
1191 except TclError:
1192 return None, None
1193
1194 # Return the text widget's current view of what a tab stop means
1195 # (equivalent width in spaces).
1196
1197 def get_tabwidth(self):
1198 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1199 return int(current)
1200
1201 # Set the text widget's current view of what a tab stop means.
1202
1203 def set_tabwidth(self, newtabwidth):
1204 text = self.text
1205 if self.get_tabwidth() != newtabwidth:
1206 pixels = text.tk.call("font", "measure", text["font"],
1207 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001208 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001209 text.configure(tabs=pixels)
1210
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001211 # If ispythonsource and guess are true, guess a good value for
1212 # indentwidth based on file content (if possible), and if
1213 # indentwidth != tabwidth set usetabs false.
1214 # In any case, adjust the Text widget's view of what a tab
1215 # character means.
1216
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001217 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001218 if guess and ispythonsource:
1219 i = self.guess_indent()
1220 if 2 <= i <= 8:
1221 self.indentwidth = i
1222 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001223 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001224 self.set_tabwidth(self.tabwidth)
1225
1226 def smart_backspace_event(self, event):
1227 text = self.text
1228 first, last = self.get_selection_indices()
1229 if first and last:
1230 text.delete(first, last)
1231 text.mark_set("insert", first)
1232 return "break"
1233 # Delete whitespace left, until hitting a real char or closest
1234 # preceding virtual tab stop.
1235 chars = text.get("insert linestart", "insert")
1236 if chars == '':
1237 if text.compare("insert", ">", "1.0"):
1238 # easy: delete preceding newline
1239 text.delete("insert-1c")
1240 else:
1241 text.bell() # at start of buffer
1242 return "break"
1243 if chars[-1] not in " \t":
1244 # easy: delete preceding real char
1245 text.delete("insert-1c")
1246 return "break"
1247 # Ick. It may require *inserting* spaces if we back up over a
1248 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001249 tabwidth = self.tabwidth
1250 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001251 assert have > 0
1252 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001253 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001254 if self.context_use_ps1:
1255 last_line_of_prompt = sys.ps1.split('\n')[-1]
1256 else:
1257 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001258 ncharsdeleted = 0
1259 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001260 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001261 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001262 chars = chars[:-1]
1263 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001264 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001265 if have <= want or chars[-1] not in " \t":
1266 break
1267 text.undo_block_start()
1268 text.delete("insert-%dc" % ncharsdeleted, "insert")
1269 if have < want:
1270 text.insert("insert", ' ' * (want - have))
1271 text.undo_block_stop()
1272 return "break"
1273
1274 def smart_indent_event(self, event):
1275 # if intraline selection:
1276 # delete it
1277 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001278 # do indent-region
1279 # else:
1280 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001281 text = self.text
1282 first, last = self.get_selection_indices()
1283 text.undo_block_start()
1284 try:
1285 if first and last:
1286 if index2line(first) != index2line(last):
1287 return self.indent_region_event(event)
1288 text.delete(first, last)
1289 text.mark_set("insert", first)
1290 prefix = text.get("insert linestart", "insert")
1291 raw, effective = classifyws(prefix, self.tabwidth)
1292 if raw == len(prefix):
1293 # only whitespace to the left
1294 self.reindent_to(effective + self.indentwidth)
1295 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001296 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001297 if self.usetabs:
1298 pad = '\t'
1299 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001300 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001301 n = self.indentwidth
1302 pad = ' ' * (n - effective % n)
1303 text.insert("insert", pad)
1304 text.see("insert")
1305 return "break"
1306 finally:
1307 text.undo_block_stop()
1308
1309 def newline_and_indent_event(self, event):
1310 text = self.text
1311 first, last = self.get_selection_indices()
1312 text.undo_block_start()
1313 try:
1314 if first and last:
1315 text.delete(first, last)
1316 text.mark_set("insert", first)
1317 line = text.get("insert linestart", "insert")
1318 i, n = 0, len(line)
1319 while i < n and line[i] in " \t":
1320 i = i+1
1321 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001322 # the cursor is in or at leading indentation in a continuation
1323 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001324 text.insert("insert linestart", '\n')
1325 return "break"
1326 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001327 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001328 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001329 last_line_of_prompt = sys.ps1.split('\n')[-1]
1330 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001331 line = line[:-1]
1332 i = i+1
1333 if i:
1334 text.delete("insert - %d chars" % i, "insert")
1335 # strip whitespace after insert point
1336 while text.get("insert") in " \t":
1337 text.delete("insert")
1338 # start new line
1339 text.insert("insert", '\n')
1340
1341 # adjust indentation for continuations and block
1342 # open/close first need to find the last stmt
1343 lno = index2line(text.index('insert'))
1344 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001345 if not self.context_use_ps1:
1346 for context in self.num_context_lines:
1347 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001348 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001349 rawtext = text.get(startatindex, "insert")
1350 y.set_str(rawtext)
1351 bod = y.find_good_parse_start(
1352 self.context_use_ps1,
1353 self._build_char_in_string_func(startatindex))
1354 if bod is not None or startat == 1:
1355 break
1356 y.set_lo(bod or 0)
1357 else:
1358 r = text.tag_prevrange("console", "insert")
1359 if r:
1360 startatindex = r[1]
1361 else:
1362 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001363 rawtext = text.get(startatindex, "insert")
1364 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001365 y.set_lo(0)
1366
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001367 c = y.get_continuation_type()
1368 if c != PyParse.C_NONE:
1369 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001370 if c == PyParse.C_STRING_FIRST_LINE:
1371 # after the first line of a string; do not indent at all
1372 pass
1373 elif c == PyParse.C_STRING_NEXT_LINES:
1374 # inside a string which started before this line;
1375 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001376 text.insert("insert", indent)
1377 elif c == PyParse.C_BRACKET:
1378 # line up with the first (if any) element of the
1379 # last open bracket structure; else indent one
1380 # level beyond the indent of the line with the
1381 # last open bracket
1382 self.reindent_to(y.compute_bracket_indent())
1383 elif c == PyParse.C_BACKSLASH:
1384 # if more than one line in this stmt already, just
1385 # mimic the current indent; else if initial line
1386 # has a start on an assignment stmt, indent to
1387 # beyond leftmost =; else to beyond first chunk of
1388 # non-whitespace on initial line
1389 if y.get_num_lines_in_stmt() > 1:
1390 text.insert("insert", indent)
1391 else:
1392 self.reindent_to(y.compute_backslash_indent())
1393 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001394 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001395 return "break"
1396
1397 # This line starts a brand new stmt; indent relative to
1398 # indentation of initial line of closest preceding
1399 # interesting stmt.
1400 indent = y.get_base_indent_string()
1401 text.insert("insert", indent)
1402 if y.is_block_opener():
1403 self.smart_indent_event(event)
1404 elif indent and y.is_block_closer():
1405 self.smart_backspace_event(event)
1406 return "break"
1407 finally:
1408 text.see("insert")
1409 text.undo_block_stop()
1410
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001411 # Our editwin provides a is_char_in_string function that works
1412 # with a Tk text index, but PyParse only knows about offsets into
1413 # a string. This builds a function for PyParse that accepts an
1414 # offset.
1415
1416 def _build_char_in_string_func(self, startindex):
1417 def inner(offset, _startindex=startindex,
1418 _icis=self.is_char_in_string):
1419 return _icis(_startindex + "+%dc" % offset)
1420 return inner
1421
1422 def indent_region_event(self, event):
1423 head, tail, chars, lines = self.get_region()
1424 for pos in range(len(lines)):
1425 line = lines[pos]
1426 if line:
1427 raw, effective = classifyws(line, self.tabwidth)
1428 effective = effective + self.indentwidth
1429 lines[pos] = self._make_blanks(effective) + line[raw:]
1430 self.set_region(head, tail, chars, lines)
1431 return "break"
1432
1433 def dedent_region_event(self, event):
1434 head, tail, chars, lines = self.get_region()
1435 for pos in range(len(lines)):
1436 line = lines[pos]
1437 if line:
1438 raw, effective = classifyws(line, self.tabwidth)
1439 effective = max(effective - self.indentwidth, 0)
1440 lines[pos] = self._make_blanks(effective) + line[raw:]
1441 self.set_region(head, tail, chars, lines)
1442 return "break"
1443
1444 def comment_region_event(self, event):
1445 head, tail, chars, lines = self.get_region()
1446 for pos in range(len(lines) - 1):
1447 line = lines[pos]
1448 lines[pos] = '##' + line
1449 self.set_region(head, tail, chars, lines)
1450
1451 def uncomment_region_event(self, event):
1452 head, tail, chars, lines = self.get_region()
1453 for pos in range(len(lines)):
1454 line = lines[pos]
1455 if not line:
1456 continue
1457 if line[:2] == '##':
1458 line = line[2:]
1459 elif line[:1] == '#':
1460 line = line[1:]
1461 lines[pos] = line
1462 self.set_region(head, tail, chars, lines)
1463
1464 def tabify_region_event(self, event):
1465 head, tail, chars, lines = self.get_region()
1466 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001467 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001468 for pos in range(len(lines)):
1469 line = lines[pos]
1470 if line:
1471 raw, effective = classifyws(line, tabwidth)
1472 ntabs, nspaces = divmod(effective, tabwidth)
1473 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1474 self.set_region(head, tail, chars, lines)
1475
1476 def untabify_region_event(self, event):
1477 head, tail, chars, lines = self.get_region()
1478 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001479 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001480 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001481 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001482 self.set_region(head, tail, chars, lines)
1483
1484 def toggle_tabs_event(self, event):
1485 if self.askyesno(
1486 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001487 "Turn tabs " + ("on", "off")[self.usetabs] +
1488 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001489 ("will be", "remains at")[self.usetabs] + " 8." +
1490 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001491 parent=self.text):
1492 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001493 # Try to prevent inconsistent indentation.
1494 # User must change indent width manually after using tabs.
1495 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001496 return "break"
1497
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001498 # XXX this isn't bound to anything -- see tabwidth comments
1499## def change_tabwidth_event(self, event):
1500## new = self._asktabwidth()
1501## if new != self.tabwidth:
1502## self.tabwidth = new
1503## self.set_indentation_params(0, guess=0)
1504## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001505
1506 def change_indentwidth_event(self, event):
1507 new = self.askinteger(
1508 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001509 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001510 parent=self.text,
1511 initialvalue=self.indentwidth,
1512 minvalue=2,
1513 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001514 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001515 self.indentwidth = new
1516 return "break"
1517
1518 def get_region(self):
1519 text = self.text
1520 first, last = self.get_selection_indices()
1521 if first and last:
1522 head = text.index(first + " linestart")
1523 tail = text.index(last + "-1c lineend +1c")
1524 else:
1525 head = text.index("insert linestart")
1526 tail = text.index("insert lineend +1c")
1527 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001528 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001529 return head, tail, chars, lines
1530
1531 def set_region(self, head, tail, chars, lines):
1532 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001533 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001534 if newchars == chars:
1535 text.bell()
1536 return
1537 text.tag_remove("sel", "1.0", "end")
1538 text.mark_set("insert", head)
1539 text.undo_block_start()
1540 text.delete(head, tail)
1541 text.insert(head, newchars)
1542 text.undo_block_stop()
1543 text.tag_add("sel", head, "insert")
1544
1545 # Make string that displays as n leading blanks.
1546
1547 def _make_blanks(self, n):
1548 if self.usetabs:
1549 ntabs, nspaces = divmod(n, self.tabwidth)
1550 return '\t' * ntabs + ' ' * nspaces
1551 else:
1552 return ' ' * n
1553
1554 # Delete from beginning of line to insert point, then reinsert
1555 # column logical (meaning use tabs if appropriate) spaces.
1556
1557 def reindent_to(self, column):
1558 text = self.text
1559 text.undo_block_start()
1560 if text.compare("insert linestart", "!=", "insert"):
1561 text.delete("insert linestart", "insert")
1562 if column:
1563 text.insert("insert", self._make_blanks(column))
1564 text.undo_block_stop()
1565
1566 def _asktabwidth(self):
1567 return self.askinteger(
1568 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001569 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001570 parent=self.text,
1571 initialvalue=self.indentwidth,
1572 minvalue=2,
Roger Serwy75b249c2013-04-06 20:26:53 -05001573 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001574
1575 # Guess indentwidth from text content.
1576 # Return guessed indentwidth. This should not be believed unless
1577 # it's in a reasonable range (e.g., it will be 0 if no indented
1578 # blocks are found).
1579
1580 def guess_indent(self):
1581 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1582 if opener and indented:
1583 raw, indentsmall = classifyws(opener, self.tabwidth)
1584 raw, indentlarge = classifyws(indented, self.tabwidth)
1585 else:
1586 indentsmall = indentlarge = 0
1587 return indentlarge - indentsmall
1588
1589# "line.col" -> line, as an int
1590def index2line(index):
1591 return int(float(index))
1592
1593# Look at the leading whitespace in s.
1594# Return pair (# of leading ws characters,
1595# effective # of leading blanks after expanding
1596# tabs to width tabwidth)
1597
1598def classifyws(s, tabwidth):
1599 raw = effective = 0
1600 for ch in s:
1601 if ch == ' ':
1602 raw = raw + 1
1603 effective = effective + 1
1604 elif ch == '\t':
1605 raw = raw + 1
1606 effective = (effective // tabwidth + 1) * tabwidth
1607 else:
1608 break
1609 return raw, effective
1610
1611import tokenize
1612_tokenize = tokenize
1613del tokenize
1614
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001615class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001616
1617 # .run() chews over the Text widget, looking for a block opener
1618 # and the stmt following it. Returns a pair,
1619 # (line containing block opener, line containing stmt)
1620 # Either or both may be None.
1621
1622 def __init__(self, text, tabwidth):
1623 self.text = text
1624 self.tabwidth = tabwidth
1625 self.i = self.finished = 0
1626 self.blkopenline = self.indentedline = None
1627
1628 def readline(self):
1629 if self.finished:
1630 return ""
1631 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001632 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001633 if self.text.compare(mark, ">=", "end"):
1634 return ""
1635 return self.text.get(mark, mark + " lineend+1c")
1636
1637 def tokeneater(self, type, token, start, end, line,
1638 INDENT=_tokenize.INDENT,
1639 NAME=_tokenize.NAME,
1640 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1641 if self.finished:
1642 pass
1643 elif type == NAME and token in OPENERS:
1644 self.blkopenline = line
1645 elif type == INDENT and self.blkopenline:
1646 self.indentedline = line
1647 self.finished = 1
1648
1649 def run(self):
1650 save_tabsize = _tokenize.tabsize
1651 _tokenize.tabsize = self.tabwidth
1652 try:
1653 try:
1654 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001655 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001656 # since we cut off the tokenizer early, we can trigger
1657 # spurious errors
1658 pass
1659 finally:
1660 _tokenize.tabsize = save_tabsize
1661 return self.blkopenline, self.indentedline
1662
1663### end autoindent code ###
1664
David Scherer7aced172000-08-15 01:13:23 +00001665def prepstr(s):
1666 # Helper to extract the underscore from a string, e.g.
1667 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001668 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001669 if i >= 0:
1670 s = s[:i] + s[i+1:]
1671 return i, s
1672
1673
1674keynames = {
1675 'bracketleft': '[',
1676 'bracketright': ']',
1677 'slash': '/',
1678}
1679
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001680def get_accelerator(keydefs, eventname):
1681 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001682 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1683 # if not keylist:
Ned Deily57847df2014-03-27 20:47:04 -07001684 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily60651532011-01-31 00:52:49 +00001685 "<<open-module>>",
1686 "<<goto-line>>",
1687 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001688 return ""
1689 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001690 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001691 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1692 s = re.sub("Key-", "", s)
1693 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1694 s = re.sub("Control-", "Ctrl-", s)
1695 s = re.sub("-", "+", s)
1696 s = re.sub("><", " ", s)
1697 s = re.sub("<", "", s)
1698 s = re.sub(">", "", s)
1699 return s
1700
1701
1702def fixwordbreaks(root):
1703 # Make sure that Tk's double-click and next/previous word
1704 # operations use our definition of a word (i.e. an identifier)
1705 tk = root.tk
1706 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1707 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1708 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1709
1710
Terry Jan Reedycf834762014-10-17 01:31:29 -04001711def _editor_window(parent): # htest #
1712 # error if close master window first - timer event, after script
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001713 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001714 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001715 if sys.argv[1:]:
1716 filename = sys.argv[1]
1717 else:
1718 filename = None
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001719 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001720 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001721 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycf834762014-10-17 01:31:29 -04001722 # Does not stop error, neither does following
1723 # edit.text.bind("<<close-window>>", edit.close_event)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001724
David Scherer7aced172000-08-15 01:13:23 +00001725
1726if __name__ == '__main__':
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001727 from idlelib.idle_test.htest import run
Terry Jan Reedy70e763c2015-09-20 19:55:44 -04001728 run(_editor_window)