blob: d69eb43e8d4dbf4440b4fb326ba9884ed4053950 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
Terry Jan Reedy59243652014-01-23 00:36:37 -05003from platform import python_version
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
12from idlelib import idlever
13from idlelib import WindowList
14from idlelib import SearchDialog
15from idlelib import GrepDialog
16from idlelib import ReplaceDialog
17from idlelib import PyParse
18from idlelib.configHandler import idleConf
19from idlelib import aboutDialog, textView, configDialog
20from idlelib import macosxSupport
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
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000025def _sphinx_version():
26 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
27 major, minor, micro, level, serial = sys.version_info
28 release = '%s%s' % (major, minor)
29 if micro:
Benjamin Petersonfb234632009-06-13 15:42:23 +000030 release += '%s' % (micro,)
31 if level == 'candidate':
32 release += 'rc%s' % (serial,)
33 elif level != 'final':
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000034 release += '%s%s' % (level[0], serial)
35 return release
36
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000037def _find_module(fullname, path=None):
38 """Version of imp.find_module() that handles hierarchical module names"""
39
40 file = None
41 for tgt in fullname.split('.'):
42 if file is not None:
43 file.close() # close intermediate files
44 (file, filename, descr) = imp.find_module(tgt, path)
45 if descr[2] == imp.PY_SOURCE:
46 break # find but not load the source file
47 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000048 try:
49 path = module.__path__
50 except AttributeError:
51 raise ImportError, 'No source for module ' + module.__name__
Raymond Hettinger179816d2011-04-12 18:54:46 -070052 if descr[2] != imp.PY_SOURCE:
53 # If all of the above fails and didn't raise an exception,fallback
54 # to a straight import which can find __init__.py in a package.
55 m = __import__(fullname)
56 try:
57 filename = m.__file__
58 except AttributeError:
59 pass
60 else:
61 file = None
62 base, ext = os.path.splitext(filename)
63 if ext == '.pyc':
64 ext = '.py'
65 filename = base + ext
66 descr = filename, None, imp.PY_SOURCE
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000067 return file, filename, descr
68
Terry Jan Reedyadb87e22012-02-05 15:10:55 -050069
70class HelpDialog(object):
71
72 def __init__(self):
73 self.parent = None # parent of help window
74 self.dlg = None # the help window iteself
75
76 def display(self, parent, near=None):
77 """ Display the help dialog.
78
79 parent - parent widget for the help window
80
81 near - a Toplevel widget (e.g. EditorWindow or PyShell)
82 to use as a reference for placing the help window
83 """
84 if self.dlg is None:
85 self.show_dialog(parent)
86 if near:
87 self.nearwindow(near)
88
89 def show_dialog(self, parent):
90 self.parent = parent
91 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
92 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
93 dlg.bind('<Destroy>', self.destroy, '+')
94
95 def nearwindow(self, near):
96 # Place the help dialog near the window specified by parent.
97 # Note - this may not reposition the window in Metacity
98 # if "/apps/metacity/general/disable_workarounds" is enabled
99 dlg = self.dlg
100 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
101 dlg.withdraw()
102 dlg.geometry("=+%d+%d" % geom)
103 dlg.deiconify()
104 dlg.lift()
105
106 def destroy(self, ev=None):
107 self.dlg = None
108 self.parent = None
109
110helpDialog = HelpDialog() # singleton instance
Terry Jan Reedy43458462014-05-19 00:12:00 -0400111def _help_dialog(parent): # wrapper for htest
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -0400112 helpDialog.show_dialog(parent)
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500113
114
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000115class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +0000116 from idlelib.Percolator import Percolator
117 from idlelib.ColorDelegator import ColorDelegator
118 from idlelib.UndoDelegator import UndoDelegator
119 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
120 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +0000121 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +0000122 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000123
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000124 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000125
126 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000127 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +0000128 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000129 if sys.platform.count('linux'):
130 # look for html docs in a couple of standard places
131 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
132 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
133 dochome = '/var/www/html/python/index.html'
134 else:
135 basepath = '/usr/share/doc/' # standard location
136 dochome = os.path.join(basepath, pyver,
137 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000138 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +0000139 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +0000140 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000141 if os.path.isfile(chmfile):
142 dochome = chmfile
Ned Deily57847df2014-03-27 20:47:04 -0700143 elif sys.platform == 'darwin':
144 # documentation may be stored inside a python framework
Ronald Oussoren19302d92006-06-11 14:33:36 +0000145 dochome = os.path.join(sys.prefix,
146 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000147 dochome = os.path.normpath(dochome)
148 if os.path.isfile(dochome):
149 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +0000150 if sys.platform == 'darwin':
151 # Safari requires real file:-URLs
152 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000153 else:
Raymond Hettinger7b4c2be2009-01-20 10:46:23 +0000154 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000155 currentTheme=idleConf.CurrentTheme()
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)
David Scherer7aced172000-08-15 01:13:23 +0000217 text.bind("<<open-module>>", self.open_module)
218 text.bind("<<do-nothing>>", lambda event: "break")
219 text.bind("<<select-all>>", self.select_all)
220 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000221 text.bind("<<find>>", self.find_event)
222 text.bind("<<find-again>>", self.find_again_event)
223 text.bind("<<find-in-files>>", self.find_in_files_event)
224 text.bind("<<find-selection>>", self.find_selection_event)
225 text.bind("<<replace>>", self.replace_event)
226 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000227 text.bind("<<smart-backspace>>",self.smart_backspace_event)
228 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
229 text.bind("<<smart-indent>>",self.smart_indent_event)
230 text.bind("<<indent-region>>",self.indent_region_event)
231 text.bind("<<dedent-region>>",self.dedent_region_event)
232 text.bind("<<comment-region>>",self.comment_region_event)
233 text.bind("<<uncomment-region>>",self.uncomment_region_event)
234 text.bind("<<tabify-region>>",self.tabify_region_event)
235 text.bind("<<untabify-region>>",self.untabify_region_event)
236 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
237 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000238 text.bind("<Left>", self.move_at_edge_if_selection(0))
239 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000240 text.bind("<<del-word-left>>", self.del_word_left)
241 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000242 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000243
David Scherer7aced172000-08-15 01:13:23 +0000244 if flist:
245 flist.inversedict[self] = key
246 if key:
247 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000248 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000249 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
250 text.bind("<<open-class-browser>>", self.open_class_browser)
251 text.bind("<<open-path-browser>>", self.open_path_browser)
252
Steven M. Gava898a3652001-10-07 11:10:44 +0000253 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000254 vbar['command'] = text.yview
255 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000256 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000257 fontWeight = 'normal'
258 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000259 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000260 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200261 idleConf.GetOption('main', 'EditorWindow',
262 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000263 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000264 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
265 text.pack(side=TOP, fill=BOTH, expand=1)
266 text.focus_set()
267
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000268 # usetabs true -> literal tab characters are used by indent and
269 # dedent cmds, possibly mixed with spaces if
270 # indentwidth is not a multiple of tabwidth,
271 # which will cause Tabnanny to nag!
272 # false -> tab characters are converted to spaces by indent
273 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000274 # Although use-spaces=0 can be configured manually in config-main.def,
275 # configuration of tabs v. spaces is not supported in the configuration
276 # dialog. IDLE promotes the preferred Python indentation: use spaces!
277 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
278 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000279
280 # tabwidth is the display width of a literal tab character.
281 # CAUTION: telling Tk to use anything other than its default
282 # tab setting causes it to use an entirely different tabbing algorithm,
283 # treating tab stops as fixed distances from the left margin.
284 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000285 self.tabwidth = 8 # must remain 8 until Tk is fixed.
286
287 # indentwidth is the number of screen characters per indent level.
288 # The recommended Python indentation is four spaces.
289 self.indentwidth = self.tabwidth
290 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000291
292 # If context_use_ps1 is true, parsing searches back for a ps1 line;
293 # else searches for a popular (if, def, ...) Python stmt.
294 self.context_use_ps1 = False
295
296 # When searching backwards for a reliable place to begin parsing,
297 # first start num_context_lines[0] lines back, then
298 # num_context_lines[1] lines back if that didn't work, and so on.
299 # The last value should be huge (larger than the # of lines in a
300 # conceivable file).
301 # Making the initial values larger slows things down more often.
302 self.num_context_lines = 50, 500, 5000000
303
David Scherer7aced172000-08-15 01:13:23 +0000304 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000305
306 self.undo = undo = self.UndoDelegator()
307 per.insertfilter(undo)
308 text.undo_block_start = undo.undo_block_start
309 text.undo_block_stop = undo.undo_block_stop
310 undo.set_saved_change_hook(self.saved_change_hook)
311
312 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000313 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000314 io.set_filename_change_hook(self.filename_change_hook)
315
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000316 # Create the recent files submenu
317 self.recent_files_menu = Menu(self.menubar)
318 self.menudict['file'].insert_cascade(3, label='Recent Files',
319 underline=0,
320 menu=self.recent_files_menu)
321 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000322
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000323 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000324 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000325 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000326 io.loadfile(filename)
327 else:
328 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000329 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000330 self.saved_change_hook()
331
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000332 self.set_indentation_params(self.ispythonsource(filename))
333
David Scherer7aced172000-08-15 01:13:23 +0000334 self.load_extensions()
335
336 menu = self.menudict.get('windows')
337 if menu:
338 end = menu.index("end")
339 if end is None:
340 end = -1
341 if end >= 0:
342 menu.add_separator()
343 end = end + 1
344 self.wmenu_end = end
345 WindowList.register_callback(self.postwindowsmenu)
346
347 # Some abstractions so IDLE extensions are cross-IDE
348 self.askyesno = tkMessageBox.askyesno
349 self.askinteger = tkSimpleDialog.askinteger
350 self.showerror = tkMessageBox.showerror
351
Roger Serwy02c0ed02013-05-20 22:13:39 -0500352 self._highlight_workaround() # Fix selection tags on Windows
353
354 def _highlight_workaround(self):
355 # On Windows, Tk removes painting of the selection
356 # tags which is different behavior than on Linux and Mac.
357 # See issue14146 for more information.
358 if not sys.platform.startswith('win'):
359 return
360
361 text = self.text
362 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
363 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
364 def highlight_fix(focus):
365 sel_range = text.tag_ranges("sel")
366 if sel_range:
367 if focus == 'out':
368 HILITE_CONFIG = idleConf.GetHighlight(
369 idleConf.CurrentTheme(), 'hilite')
370 text.tag_config("sel_fix", HILITE_CONFIG)
371 text.tag_raise("sel_fix")
372 text.tag_add("sel_fix", *sel_range)
373 elif focus == 'in':
374 text.tag_remove("sel_fix", "1.0", "end")
375
376 text.bind("<<Highlight-FocusOut>>",
377 lambda ev: highlight_fix("out"))
378 text.bind("<<Highlight-FocusIn>>",
379 lambda ev: highlight_fix("in"))
380
381
Martin v. Löwis307021f2005-11-27 16:59:04 +0000382 def _filename_to_unicode(self, filename):
383 """convert filename to unicode in order to display it in Tk"""
384 if isinstance(filename, unicode) or not filename:
385 return filename
386 else:
387 try:
388 return filename.decode(self.filesystemencoding)
389 except UnicodeDecodeError:
390 # XXX
391 try:
392 return filename.decode(self.encoding)
393 except UnicodeDecodeError:
394 # byte-to-byte conversion
395 return filename.decode('iso8859-1')
396
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000397 def new_callback(self, event):
398 dirname, basename = self.io.defaultfilename()
399 self.flist.new(dirname)
400 return "break"
401
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000402 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400403 if (event.state & 4) != 0 and event.keysym == "Home":
404 # state&4==Control. If <Control-Home>, use the Tk binding.
405 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000406 if self.text.index("iomark") and \
407 self.text.compare("iomark", "<=", "insert lineend") and \
408 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400409 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000410 insertpt = int(self.text.index("iomark").split(".")[1])
411 else:
412 line = self.text.get("insert linestart", "insert lineend")
413 for insertpt in xrange(len(line)):
414 if line[insertpt] not in (' ','\t'):
415 break
416 else:
417 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000418 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000419 if insertpt == lineat:
420 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000421 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000422 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400423 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000424 self.text.tag_remove("sel", "1.0", "end")
425 else:
426 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400427 self.text.mark_set("my_anchor", "insert") # there was no previous selection
428 else:
429 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
430 self.text.mark_set("my_anchor", "sel.first") # extend back
431 else:
432 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000433 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400434 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000435 if self.text.compare(first,">",last):
436 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000437 self.text.tag_remove("sel", "1.0", "end")
438 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000439 self.text.mark_set("insert", dest)
440 self.text.see("insert")
441 return "break"
442
David Scherer7aced172000-08-15 01:13:23 +0000443 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000444 self.status_bar = self.MultiStatusBar(self.top)
Ned Deily57847df2014-03-27 20:47:04 -0700445 if sys.platform == "darwin":
Ronald Oussoren19302d92006-06-11 14:33:36 +0000446 # Insert some padding to avoid obscuring some of the statusbar
447 # by the resize widget.
448 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000449 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
450 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
451 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000452 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
453 self.text.event_add("<<set-line-and-column>>",
454 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000455 self.text.after_idle(self.set_line_and_column)
456
457 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000458 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000459 self.status_bar.set_label('column', 'Col: %s' % column)
460 self.status_bar.set_label('line', 'Ln: %s' % line)
461
David Scherer7aced172000-08-15 01:13:23 +0000462 menu_specs = [
463 ("file", "_File"),
464 ("edit", "_Edit"),
465 ("format", "F_ormat"),
466 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000467 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000468 ("windows", "_Windows"),
469 ("help", "_Help"),
470 ]
471
Ned Deily57847df2014-03-27 20:47:04 -0700472 if sys.platform == "darwin":
Ronald Oussoren19302d92006-06-11 14:33:36 +0000473 menu_specs[-2] = ("windows", "_Window")
474
475
David Scherer7aced172000-08-15 01:13:23 +0000476 def createmenubar(self):
477 mbar = self.menubar
478 self.menudict = menudict = {}
479 for name, label in self.menu_specs:
480 underline, label = prepstr(label)
481 menudict[name] = menu = Menu(mbar, name=name)
482 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000483
Ned Deily57847df2014-03-27 20:47:04 -0700484 if macosxSupport.isCarbonTk():
Ronald Oussoren19302d92006-06-11 14:33:36 +0000485 # Insert the application menu
486 menudict['application'] = menu = Menu(mbar, name='apple')
487 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')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000571
David Scherer7aced172000-08-15 01:13:23 +0000572 def help_dialog(self, event=None):
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500573 if self.root:
574 parent = self.root
575 else:
576 parent = self.top
577 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000578
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000579 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000580 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000581 try:
582 os.startfile(self.help_url)
583 except WindowsError as why:
584 tkMessageBox.showerror(title='Document Start Failure',
585 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000586 else:
587 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000588 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000589
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000590 def cut(self,event):
591 self.text.event_generate("<<Cut>>")
592 return "break"
593
594 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000595 if not self.text.tag_ranges("sel"):
596 # There is no selection, so do nothing and maybe interrupt.
597 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000598 self.text.event_generate("<<Copy>>")
599 return "break"
600
601 def paste(self,event):
602 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000603 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000604 return "break"
605
David Scherer7aced172000-08-15 01:13:23 +0000606 def select_all(self, event=None):
607 self.text.tag_add("sel", "1.0", "end-1c")
608 self.text.mark_set("insert", "1.0")
609 self.text.see("insert")
610 return "break"
611
612 def remove_selection(self, event=None):
613 self.text.tag_remove("sel", "1.0", "end")
614 self.text.see("insert")
615
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000616 def move_at_edge_if_selection(self, edge_index):
617 """Cursor move begins at start or end of selection
618
619 When a left/right cursor key is pressed create and return to Tkinter a
620 function which causes a cursor move from the associated edge of the
621 selection.
622
623 """
624 self_text_index = self.text.index
625 self_text_mark_set = self.text.mark_set
626 edges_table = ("sel.first+1c", "sel.last-1c")
627 def move_at_edge(event):
628 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
629 try:
630 self_text_index("sel.first")
631 self_text_mark_set("insert", edges_table[edge_index])
632 except TclError:
633 pass
634 return move_at_edge
635
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000636 def del_word_left(self, event):
637 self.text.event_generate('<Meta-Delete>')
638 return "break"
639
640 def del_word_right(self, event):
641 self.text.event_generate('<Meta-d>')
642 return "break"
643
Steven M. Gavac5976402002-01-04 03:06:08 +0000644 def find_event(self, event):
645 SearchDialog.find(self.text)
646 return "break"
647
648 def find_again_event(self, event):
649 SearchDialog.find_again(self.text)
650 return "break"
651
652 def find_selection_event(self, event):
653 SearchDialog.find_selection(self.text)
654 return "break"
655
656 def find_in_files_event(self, event):
657 GrepDialog.grep(self.text, self.io, self.flist)
658 return "break"
659
660 def replace_event(self, event):
661 ReplaceDialog.replace(self.text)
662 return "break"
663
664 def goto_line_event(self, event):
665 text = self.text
666 lineno = tkSimpleDialog.askinteger("Goto",
667 "Go to line number:",parent=text)
668 if lineno is None:
669 return "break"
670 if lineno <= 0:
671 text.bell()
672 return "break"
673 text.mark_set("insert", "%d.0" % lineno)
674 text.see("insert")
675
David Scherer7aced172000-08-15 01:13:23 +0000676 def open_module(self, event=None):
677 # XXX Shouldn't this be in IOBinding or in FileList?
678 try:
679 name = self.text.get("sel.first", "sel.last")
680 except TclError:
681 name = ""
682 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000683 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000684 name = tkSimpleDialog.askstring("Module",
685 "Enter the name of a Python module\n"
686 "to search on sys.path and open:",
687 parent=self.text, initialvalue=name)
688 if name:
689 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000690 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000691 return
David Scherer7aced172000-08-15 01:13:23 +0000692 # XXX Ought to insert current file's directory in front of path
693 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000694 (f, file, (suffix, mode, type)) = _find_module(name)
Terry Jan Reedy2b149862013-06-29 00:59:34 -0400695 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000696 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
697 return
698 if type != imp.PY_SOURCE:
699 tkMessageBox.showerror("Unsupported type",
700 "%s is not a source module" % name, parent=self.text)
701 return
702 if f:
703 f.close()
704 if self.flist:
705 self.flist.open(file)
706 else:
707 self.io.loadfile(file)
708
709 def open_class_browser(self, event=None):
710 filename = self.io.filename
711 if not filename:
712 tkMessageBox.showerror(
713 "No filename",
714 "This buffer has no associated filename",
715 master=self.text)
716 self.text.focus_set()
717 return None
718 head, tail = os.path.split(filename)
719 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000720 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000721 ClassBrowser.ClassBrowser(self.flist, base, [head])
722
723 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000724 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000725 PathBrowser.PathBrowser(self.flist)
726
727 def gotoline(self, lineno):
728 if lineno is not None and lineno > 0:
729 self.text.mark_set("insert", "%d.0" % lineno)
730 self.text.tag_remove("sel", "1.0", "end")
731 self.text.tag_add("sel", "insert", "insert +1l")
732 self.center()
733
734 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000735 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000736 return True
David Scherer7aced172000-08-15 01:13:23 +0000737 base, ext = os.path.splitext(os.path.basename(filename))
738 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000739 return True
David Scherer7aced172000-08-15 01:13:23 +0000740 try:
741 f = open(filename)
742 line = f.readline()
743 f.close()
744 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000745 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000746 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000747
748 def close_hook(self):
749 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000750 self.flist.unregister_maybe_terminate(self)
751 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000752
753 def set_close_hook(self, close_hook):
754 self.close_hook = close_hook
755
756 def filename_change_hook(self):
757 if self.flist:
758 self.flist.filename_changed_edit(self)
759 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000760 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000761 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000762
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000763 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000764 if self.color:
765 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000766 if self.ispythonsource(self.io.filename):
767 self.color = self.ColorDelegator()
768 # can add more colorizers here...
769 if self.color:
770 self.per.removefilter(self.undo)
771 self.per.insertfilter(self.color)
772 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000773
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000774 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000775 if not self.color:
776 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000777 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000778 self.per.removefilter(self.color)
779 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000780
Steven M. Gavab77d3432002-03-02 07:16:21 +0000781 def ResetColorizer(self):
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000782 "Update the colour theme"
783 # Called from self.filename_change_hook and from configDialog.py
784 self._rmcolorizer()
785 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000786 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000787 normal_colors = idleConf.GetHighlight(theme, 'normal')
788 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
789 select_colors = idleConf.GetHighlight(theme, 'hilite')
790 self.text.config(
791 foreground=normal_colors['foreground'],
792 background=normal_colors['background'],
793 insertbackground=cursor_color,
794 selectforeground=select_colors['foreground'],
795 selectbackground=select_colors['background'],
796 )
David Scherer7aced172000-08-15 01:13:23 +0000797
Steven M. Gavab1585412002-03-12 00:21:56 +0000798 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000799 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000800 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000801 fontWeight='normal'
802 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
803 fontWeight='bold'
804 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200805 idleConf.GetOption('main','EditorWindow','font-size',
806 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000807 fontWeight))
808
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000809 def RemoveKeybindings(self):
810 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000811 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000812 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000813 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000814 self.text.event_delete(event, *keylist)
815 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000816 xkeydefs = idleConf.GetExtensionBindings(extensionName)
817 if xkeydefs:
818 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000819 self.text.event_delete(event, *keylist)
820
821 def ApplyKeybindings(self):
822 "Update the keybindings after they are changed"
823 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000824 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000825 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000826 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000827 xkeydefs = idleConf.GetExtensionBindings(extensionName)
828 if xkeydefs:
829 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000830 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000831 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000832 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000833 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000834 for item in menu[1]:
835 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000836 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000837 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000838 menu = self.menudict[menubarItem]
Ned Deily14ef0c82013-07-20 14:38:24 -0700839 end = menu.index(END)
840 if end is None:
841 # Skip empty menus
842 continue
843 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000844 for index in range(0, end):
845 if menu.type(index) == 'command':
846 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000847 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000848 itemName = menu.entrycget(index, 'label')
849 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000850 if menubarItem in menuEventDict:
851 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000852 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000853 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000854 accel = get_accelerator(keydefs, event)
855 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000856
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000857 def set_notabs_indentwidth(self):
858 "Update the indentwidth if changed and not using tabs in this window"
859 # Called from configDialog.py
860 if not self.usetabs:
861 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
862 type='int')
863
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000864 def reset_help_menu_entries(self):
865 "Update the additional help entries on the Help menu"
866 help_list = idleConf.GetAllExtraHelpSourcesList()
867 helpmenu = self.menudict['help']
868 # first delete the extra help entries, if any
869 helpmenu_length = helpmenu.index(END)
870 if helpmenu_length > self.base_helpmenu_length:
871 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
872 # then rebuild them
873 if help_list:
874 helpmenu.add_separator()
875 for entry in help_list:
876 cmd = self.__extra_help_callback(entry[1])
877 helpmenu.add_command(label=entry[0], command=cmd)
878 # and update the menu dictionary
879 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000880
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000881 def __extra_help_callback(self, helpfile):
882 "Create a callback with the helpfile value frozen at definition time"
883 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000884 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000885 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000886 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000887 try:
888 os.startfile(helpfile)
889 except WindowsError as why:
890 tkMessageBox.showerror(title='Document Start Failure',
891 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000892 else:
893 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000894 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000895
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000896 def update_recent_files_list(self, new_file=None):
897 "Load and update the recent files list and menus"
898 rf_list = []
899 if os.path.exists(self.recent_files_path):
Terry Jan Reedyf9489432013-08-04 15:39:56 -0400900 with open(self.recent_files_path, 'r') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000901 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000902 if new_file:
903 new_file = os.path.abspath(new_file) + '\n'
904 if new_file in rf_list:
905 rf_list.remove(new_file) # move to top
906 rf_list.insert(0, new_file)
907 # clean and save the recent files list
908 bad_paths = []
909 for path in rf_list:
910 if '\0' in path or not os.path.exists(path[0:-1]):
911 bad_paths.append(path)
912 rf_list = [path for path in rf_list if path not in bad_paths]
913 ulchars = "1234567890ABCDEFGHIJK"
914 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000915 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800916 with open(self.recent_files_path, 'w') as rf_file:
917 rf_file.writelines(rf_list)
918 except IOError as err:
919 if not getattr(self.root, "recentfilelist_error_displayed", False):
920 self.root.recentfilelist_error_displayed = True
921 tkMessageBox.showerror(title='IDLE Error',
922 message='Unable to update Recent Files list:\n%s'
923 % str(err),
924 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000925 # for each edit window instance, construct the recent files menu
926 for instance in self.top.instance_dict.keys():
927 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700928 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000929 for i, file_name in enumerate(rf_list):
930 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000931 # make unicode string to display non-ASCII chars correctly
932 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000933 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000934 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000935 command=callback,
936 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000937
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000938 def __recent_file_callback(self, file_name):
939 def open_recent_file(fn_closure=file_name):
940 self.io.open(editFile=fn_closure)
941 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000942
David Scherer7aced172000-08-15 01:13:23 +0000943 def saved_change_hook(self):
944 short = self.short_title()
945 long = self.long_title()
946 if short and long:
947 title = short + " - " + long
948 elif short:
949 title = short
950 elif long:
951 title = long
952 else:
953 title = "Untitled"
954 icon = short or long or title
955 if not self.get_saved():
956 title = "*%s*" % title
957 icon = "*%s" % icon
958 self.top.wm_title(title)
959 self.top.wm_iconname(icon)
960
961 def get_saved(self):
962 return self.undo.get_saved()
963
964 def set_saved(self, flag):
965 self.undo.set_saved(flag)
966
967 def reset_undo(self):
968 self.undo.reset_undo()
969
970 def short_title(self):
Terry Jan Reedy59243652014-01-23 00:36:37 -0500971 pyversion = "Python " + python_version() + ": "
David Scherer7aced172000-08-15 01:13:23 +0000972 filename = self.io.filename
973 if filename:
974 filename = os.path.basename(filename)
Terry Jan Reedy59243652014-01-23 00:36:37 -0500975 else:
976 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000977 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy59243652014-01-23 00:36:37 -0500978 return pyversion + self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000979
980 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000981 # return unicode string to display non-ASCII chars correctly
982 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000983
984 def center_insert_event(self, event):
985 self.center()
986
987 def center(self, mark="insert"):
988 text = self.text
989 top, bot = self.getwindowlines()
990 lineno = self.getlineno(mark)
991 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000992 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000993 text.yview(float(newtop))
994
995 def getwindowlines(self):
996 text = self.text
997 top = self.getlineno("@0,0")
998 bot = self.getlineno("@0,65535")
999 if top == bot and text.winfo_height() == 1:
1000 # Geometry manager hasn't run yet
1001 height = int(text['height'])
1002 bot = top + height - 1
1003 return top, bot
1004
1005 def getlineno(self, mark="insert"):
1006 text = self.text
1007 return int(float(text.index(mark)))
1008
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001009 def get_geometry(self):
1010 "Return (width, height, x, y)"
1011 geom = self.top.wm_geometry()
1012 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
1013 tuple = (map(int, m.groups()))
1014 return tuple
1015
David Scherer7aced172000-08-15 01:13:23 +00001016 def close_event(self, event):
1017 self.close()
1018
1019 def maybesave(self):
1020 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001021 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001022 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001023 self.top.deiconify()
1024 self.top.lower()
1025 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001026 return self.io.maybesave()
1027
1028 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001029 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +00001030 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001031 self._close()
1032 return reply
1033
1034 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001035 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001036 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001037 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001038 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001039 self.io.close()
1040 self.io = None
1041 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001042 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001043 self.color.close(False)
1044 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001045 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001046 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001047 self.per.close()
1048 self.per = None
1049 self.top.destroy()
1050 if self.close_hook:
1051 # unless override: unregister from flist, terminate if last window
1052 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001053
1054 def load_extensions(self):
1055 self.extensions = {}
1056 self.load_standard_extensions()
1057
1058 def unload_extensions(self):
1059 for ins in self.extensions.values():
1060 if hasattr(ins, "close"):
1061 ins.close()
1062 self.extensions = {}
1063
1064 def load_standard_extensions(self):
1065 for name in self.get_standard_extension_names():
1066 try:
1067 self.load_extension(name)
1068 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001069 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001070 import traceback
1071 traceback.print_exc()
1072
1073 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001074 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001075
1076 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001077 try:
1078 mod = __import__(name, globals(), locals(), [])
1079 except ImportError:
1080 print "\nFailed to import extension: ", name
1081 return
David Scherer7aced172000-08-15 01:13:23 +00001082 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001083 keydefs = idleConf.GetExtensionBindings(name)
1084 if hasattr(cls, "menudefs"):
1085 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001086 ins = cls(self)
1087 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001088 if keydefs:
1089 self.apply_bindings(keydefs)
1090 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001091 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001092 while methodname[:1] == '<':
1093 methodname = methodname[1:]
1094 while methodname[-1:] == '>':
1095 methodname = methodname[:-1]
1096 methodname = methodname + "_event"
1097 if hasattr(ins, methodname):
1098 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001099
1100 def apply_bindings(self, keydefs=None):
1101 if keydefs is None:
1102 keydefs = self.Bindings.default_keydefs
1103 text = self.text
1104 text.keydefs = keydefs
1105 for event, keylist in keydefs.items():
1106 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001107 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001108
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001109 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001110 """Add appropriate entries to the menus and submenus
1111
1112 Menus that are absent or None in self.menudict are ignored.
1113 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001114 if menudefs is None:
1115 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001116 if keydefs is None:
1117 keydefs = self.Bindings.default_keydefs
1118 menudict = self.menudict
1119 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001120 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001121 menu = menudict.get(mname)
1122 if not menu:
1123 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001124 for entry in entrylist:
1125 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001126 menu.add_separator()
1127 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001128 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001129 checkbutton = (label[:1] == '!')
1130 if checkbutton:
1131 label = label[1:]
1132 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001133 accelerator = get_accelerator(keydefs, eventname)
1134 def command(text=text, eventname=eventname):
1135 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001136 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001137 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001138 menu.add_checkbutton(label=label, underline=underline,
1139 command=command, accelerator=accelerator,
1140 variable=var)
1141 else:
1142 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001143 command=command,
1144 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001145
1146 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001147 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001148 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001149 value = var.get()
1150 return value
1151 else:
1152 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001153
1154 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001155 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001156 if var:
1157 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001158 else:
1159 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001160
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001161 def get_var_obj(self, name, vartype=None):
1162 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001163 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001164 # create a Tkinter variable object with self.text as master:
1165 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001166 return var
1167
1168 # Tk implementations of "virtual text methods" -- each platform
1169 # reusing IDLE's support code needs to define these for its GUI's
1170 # flavor of widget.
1171
1172 # Is character at text_index in a Python string? Return 0 for
1173 # "guaranteed no", true for anything else. This info is expensive
1174 # to compute ab initio, but is probably already known by the
1175 # platform's colorizer.
1176
1177 def is_char_in_string(self, text_index):
1178 if self.color:
1179 # Return true iff colorizer hasn't (re)gotten this far
1180 # yet, or the character is tagged as being in a string
1181 return self.text.tag_prevrange("TODO", text_index) or \
1182 "STRING" in self.text.tag_names(text_index)
1183 else:
1184 # The colorizer is missing: assume the worst
1185 return 1
1186
1187 # If a selection is defined in the text widget, return (start,
1188 # end) as Tkinter text indices, otherwise return (None, None)
1189 def get_selection_indices(self):
1190 try:
1191 first = self.text.index("sel.first")
1192 last = self.text.index("sel.last")
1193 return first, last
1194 except TclError:
1195 return None, None
1196
1197 # Return the text widget's current view of what a tab stop means
1198 # (equivalent width in spaces).
1199
1200 def get_tabwidth(self):
1201 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1202 return int(current)
1203
1204 # Set the text widget's current view of what a tab stop means.
1205
1206 def set_tabwidth(self, newtabwidth):
1207 text = self.text
1208 if self.get_tabwidth() != newtabwidth:
1209 pixels = text.tk.call("font", "measure", text["font"],
1210 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001211 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001212 text.configure(tabs=pixels)
1213
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001214 # If ispythonsource and guess are true, guess a good value for
1215 # indentwidth based on file content (if possible), and if
1216 # indentwidth != tabwidth set usetabs false.
1217 # In any case, adjust the Text widget's view of what a tab
1218 # character means.
1219
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001220 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001221 if guess and ispythonsource:
1222 i = self.guess_indent()
1223 if 2 <= i <= 8:
1224 self.indentwidth = i
1225 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001226 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001227 self.set_tabwidth(self.tabwidth)
1228
1229 def smart_backspace_event(self, event):
1230 text = self.text
1231 first, last = self.get_selection_indices()
1232 if first and last:
1233 text.delete(first, last)
1234 text.mark_set("insert", first)
1235 return "break"
1236 # Delete whitespace left, until hitting a real char or closest
1237 # preceding virtual tab stop.
1238 chars = text.get("insert linestart", "insert")
1239 if chars == '':
1240 if text.compare("insert", ">", "1.0"):
1241 # easy: delete preceding newline
1242 text.delete("insert-1c")
1243 else:
1244 text.bell() # at start of buffer
1245 return "break"
1246 if chars[-1] not in " \t":
1247 # easy: delete preceding real char
1248 text.delete("insert-1c")
1249 return "break"
1250 # Ick. It may require *inserting* spaces if we back up over a
1251 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001252 tabwidth = self.tabwidth
1253 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001254 assert have > 0
1255 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001256 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001257 if self.context_use_ps1:
1258 last_line_of_prompt = sys.ps1.split('\n')[-1]
1259 else:
1260 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001261 ncharsdeleted = 0
1262 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001263 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001264 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001265 chars = chars[:-1]
1266 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001267 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001268 if have <= want or chars[-1] not in " \t":
1269 break
1270 text.undo_block_start()
1271 text.delete("insert-%dc" % ncharsdeleted, "insert")
1272 if have < want:
1273 text.insert("insert", ' ' * (want - have))
1274 text.undo_block_stop()
1275 return "break"
1276
1277 def smart_indent_event(self, event):
1278 # if intraline selection:
1279 # delete it
1280 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001281 # do indent-region
1282 # else:
1283 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001284 text = self.text
1285 first, last = self.get_selection_indices()
1286 text.undo_block_start()
1287 try:
1288 if first and last:
1289 if index2line(first) != index2line(last):
1290 return self.indent_region_event(event)
1291 text.delete(first, last)
1292 text.mark_set("insert", first)
1293 prefix = text.get("insert linestart", "insert")
1294 raw, effective = classifyws(prefix, self.tabwidth)
1295 if raw == len(prefix):
1296 # only whitespace to the left
1297 self.reindent_to(effective + self.indentwidth)
1298 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001299 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001300 if self.usetabs:
1301 pad = '\t'
1302 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001303 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001304 n = self.indentwidth
1305 pad = ' ' * (n - effective % n)
1306 text.insert("insert", pad)
1307 text.see("insert")
1308 return "break"
1309 finally:
1310 text.undo_block_stop()
1311
1312 def newline_and_indent_event(self, event):
1313 text = self.text
1314 first, last = self.get_selection_indices()
1315 text.undo_block_start()
1316 try:
1317 if first and last:
1318 text.delete(first, last)
1319 text.mark_set("insert", first)
1320 line = text.get("insert linestart", "insert")
1321 i, n = 0, len(line)
1322 while i < n and line[i] in " \t":
1323 i = i+1
1324 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001325 # the cursor is in or at leading indentation in a continuation
1326 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001327 text.insert("insert linestart", '\n')
1328 return "break"
1329 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001330 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001331 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001332 last_line_of_prompt = sys.ps1.split('\n')[-1]
1333 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001334 line = line[:-1]
1335 i = i+1
1336 if i:
1337 text.delete("insert - %d chars" % i, "insert")
1338 # strip whitespace after insert point
1339 while text.get("insert") in " \t":
1340 text.delete("insert")
1341 # start new line
1342 text.insert("insert", '\n')
1343
1344 # adjust indentation for continuations and block
1345 # open/close first need to find the last stmt
1346 lno = index2line(text.index('insert'))
1347 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001348 if not self.context_use_ps1:
1349 for context in self.num_context_lines:
1350 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001351 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001352 rawtext = text.get(startatindex, "insert")
1353 y.set_str(rawtext)
1354 bod = y.find_good_parse_start(
1355 self.context_use_ps1,
1356 self._build_char_in_string_func(startatindex))
1357 if bod is not None or startat == 1:
1358 break
1359 y.set_lo(bod or 0)
1360 else:
1361 r = text.tag_prevrange("console", "insert")
1362 if r:
1363 startatindex = r[1]
1364 else:
1365 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001366 rawtext = text.get(startatindex, "insert")
1367 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001368 y.set_lo(0)
1369
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001370 c = y.get_continuation_type()
1371 if c != PyParse.C_NONE:
1372 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001373 if c == PyParse.C_STRING_FIRST_LINE:
1374 # after the first line of a string; do not indent at all
1375 pass
1376 elif c == PyParse.C_STRING_NEXT_LINES:
1377 # inside a string which started before this line;
1378 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001379 text.insert("insert", indent)
1380 elif c == PyParse.C_BRACKET:
1381 # line up with the first (if any) element of the
1382 # last open bracket structure; else indent one
1383 # level beyond the indent of the line with the
1384 # last open bracket
1385 self.reindent_to(y.compute_bracket_indent())
1386 elif c == PyParse.C_BACKSLASH:
1387 # if more than one line in this stmt already, just
1388 # mimic the current indent; else if initial line
1389 # has a start on an assignment stmt, indent to
1390 # beyond leftmost =; else to beyond first chunk of
1391 # non-whitespace on initial line
1392 if y.get_num_lines_in_stmt() > 1:
1393 text.insert("insert", indent)
1394 else:
1395 self.reindent_to(y.compute_backslash_indent())
1396 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001397 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001398 return "break"
1399
1400 # This line starts a brand new stmt; indent relative to
1401 # indentation of initial line of closest preceding
1402 # interesting stmt.
1403 indent = y.get_base_indent_string()
1404 text.insert("insert", indent)
1405 if y.is_block_opener():
1406 self.smart_indent_event(event)
1407 elif indent and y.is_block_closer():
1408 self.smart_backspace_event(event)
1409 return "break"
1410 finally:
1411 text.see("insert")
1412 text.undo_block_stop()
1413
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001414 # Our editwin provides a is_char_in_string function that works
1415 # with a Tk text index, but PyParse only knows about offsets into
1416 # a string. This builds a function for PyParse that accepts an
1417 # offset.
1418
1419 def _build_char_in_string_func(self, startindex):
1420 def inner(offset, _startindex=startindex,
1421 _icis=self.is_char_in_string):
1422 return _icis(_startindex + "+%dc" % offset)
1423 return inner
1424
1425 def indent_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 = effective + self.indentwidth
1432 lines[pos] = self._make_blanks(effective) + line[raw:]
1433 self.set_region(head, tail, chars, lines)
1434 return "break"
1435
1436 def dedent_region_event(self, event):
1437 head, tail, chars, lines = self.get_region()
1438 for pos in range(len(lines)):
1439 line = lines[pos]
1440 if line:
1441 raw, effective = classifyws(line, self.tabwidth)
1442 effective = max(effective - self.indentwidth, 0)
1443 lines[pos] = self._make_blanks(effective) + line[raw:]
1444 self.set_region(head, tail, chars, lines)
1445 return "break"
1446
1447 def comment_region_event(self, event):
1448 head, tail, chars, lines = self.get_region()
1449 for pos in range(len(lines) - 1):
1450 line = lines[pos]
1451 lines[pos] = '##' + line
1452 self.set_region(head, tail, chars, lines)
1453
1454 def uncomment_region_event(self, event):
1455 head, tail, chars, lines = self.get_region()
1456 for pos in range(len(lines)):
1457 line = lines[pos]
1458 if not line:
1459 continue
1460 if line[:2] == '##':
1461 line = line[2:]
1462 elif line[:1] == '#':
1463 line = line[1:]
1464 lines[pos] = line
1465 self.set_region(head, tail, chars, lines)
1466
1467 def tabify_region_event(self, event):
1468 head, tail, chars, lines = self.get_region()
1469 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001470 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001471 for pos in range(len(lines)):
1472 line = lines[pos]
1473 if line:
1474 raw, effective = classifyws(line, tabwidth)
1475 ntabs, nspaces = divmod(effective, tabwidth)
1476 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1477 self.set_region(head, tail, chars, lines)
1478
1479 def untabify_region_event(self, event):
1480 head, tail, chars, lines = self.get_region()
1481 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001482 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001483 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001484 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001485 self.set_region(head, tail, chars, lines)
1486
1487 def toggle_tabs_event(self, event):
1488 if self.askyesno(
1489 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001490 "Turn tabs " + ("on", "off")[self.usetabs] +
1491 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001492 ("will be", "remains at")[self.usetabs] + " 8." +
1493 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001494 parent=self.text):
1495 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001496 # Try to prevent inconsistent indentation.
1497 # User must change indent width manually after using tabs.
1498 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001499 return "break"
1500
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001501 # XXX this isn't bound to anything -- see tabwidth comments
1502## def change_tabwidth_event(self, event):
1503## new = self._asktabwidth()
1504## if new != self.tabwidth:
1505## self.tabwidth = new
1506## self.set_indentation_params(0, guess=0)
1507## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001508
1509 def change_indentwidth_event(self, event):
1510 new = self.askinteger(
1511 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001512 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001513 parent=self.text,
1514 initialvalue=self.indentwidth,
1515 minvalue=2,
1516 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001517 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001518 self.indentwidth = new
1519 return "break"
1520
1521 def get_region(self):
1522 text = self.text
1523 first, last = self.get_selection_indices()
1524 if first and last:
1525 head = text.index(first + " linestart")
1526 tail = text.index(last + "-1c lineend +1c")
1527 else:
1528 head = text.index("insert linestart")
1529 tail = text.index("insert lineend +1c")
1530 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001531 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001532 return head, tail, chars, lines
1533
1534 def set_region(self, head, tail, chars, lines):
1535 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001536 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001537 if newchars == chars:
1538 text.bell()
1539 return
1540 text.tag_remove("sel", "1.0", "end")
1541 text.mark_set("insert", head)
1542 text.undo_block_start()
1543 text.delete(head, tail)
1544 text.insert(head, newchars)
1545 text.undo_block_stop()
1546 text.tag_add("sel", head, "insert")
1547
1548 # Make string that displays as n leading blanks.
1549
1550 def _make_blanks(self, n):
1551 if self.usetabs:
1552 ntabs, nspaces = divmod(n, self.tabwidth)
1553 return '\t' * ntabs + ' ' * nspaces
1554 else:
1555 return ' ' * n
1556
1557 # Delete from beginning of line to insert point, then reinsert
1558 # column logical (meaning use tabs if appropriate) spaces.
1559
1560 def reindent_to(self, column):
1561 text = self.text
1562 text.undo_block_start()
1563 if text.compare("insert linestart", "!=", "insert"):
1564 text.delete("insert linestart", "insert")
1565 if column:
1566 text.insert("insert", self._make_blanks(column))
1567 text.undo_block_stop()
1568
1569 def _asktabwidth(self):
1570 return self.askinteger(
1571 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001572 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001573 parent=self.text,
1574 initialvalue=self.indentwidth,
1575 minvalue=2,
Roger Serwy75b249c2013-04-06 20:26:53 -05001576 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001577
1578 # Guess indentwidth from text content.
1579 # Return guessed indentwidth. This should not be believed unless
1580 # it's in a reasonable range (e.g., it will be 0 if no indented
1581 # blocks are found).
1582
1583 def guess_indent(self):
1584 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1585 if opener and indented:
1586 raw, indentsmall = classifyws(opener, self.tabwidth)
1587 raw, indentlarge = classifyws(indented, self.tabwidth)
1588 else:
1589 indentsmall = indentlarge = 0
1590 return indentlarge - indentsmall
1591
1592# "line.col" -> line, as an int
1593def index2line(index):
1594 return int(float(index))
1595
1596# Look at the leading whitespace in s.
1597# Return pair (# of leading ws characters,
1598# effective # of leading blanks after expanding
1599# tabs to width tabwidth)
1600
1601def classifyws(s, tabwidth):
1602 raw = effective = 0
1603 for ch in s:
1604 if ch == ' ':
1605 raw = raw + 1
1606 effective = effective + 1
1607 elif ch == '\t':
1608 raw = raw + 1
1609 effective = (effective // tabwidth + 1) * tabwidth
1610 else:
1611 break
1612 return raw, effective
1613
1614import tokenize
1615_tokenize = tokenize
1616del tokenize
1617
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001618class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001619
1620 # .run() chews over the Text widget, looking for a block opener
1621 # and the stmt following it. Returns a pair,
1622 # (line containing block opener, line containing stmt)
1623 # Either or both may be None.
1624
1625 def __init__(self, text, tabwidth):
1626 self.text = text
1627 self.tabwidth = tabwidth
1628 self.i = self.finished = 0
1629 self.blkopenline = self.indentedline = None
1630
1631 def readline(self):
1632 if self.finished:
1633 return ""
1634 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001635 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001636 if self.text.compare(mark, ">=", "end"):
1637 return ""
1638 return self.text.get(mark, mark + " lineend+1c")
1639
1640 def tokeneater(self, type, token, start, end, line,
1641 INDENT=_tokenize.INDENT,
1642 NAME=_tokenize.NAME,
1643 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1644 if self.finished:
1645 pass
1646 elif type == NAME and token in OPENERS:
1647 self.blkopenline = line
1648 elif type == INDENT and self.blkopenline:
1649 self.indentedline = line
1650 self.finished = 1
1651
1652 def run(self):
1653 save_tabsize = _tokenize.tabsize
1654 _tokenize.tabsize = self.tabwidth
1655 try:
1656 try:
1657 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001658 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001659 # since we cut off the tokenizer early, we can trigger
1660 # spurious errors
1661 pass
1662 finally:
1663 _tokenize.tabsize = save_tabsize
1664 return self.blkopenline, self.indentedline
1665
1666### end autoindent code ###
1667
David Scherer7aced172000-08-15 01:13:23 +00001668def prepstr(s):
1669 # Helper to extract the underscore from a string, e.g.
1670 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001671 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001672 if i >= 0:
1673 s = s[:i] + s[i+1:]
1674 return i, s
1675
1676
1677keynames = {
1678 'bracketleft': '[',
1679 'bracketright': ']',
1680 'slash': '/',
1681}
1682
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001683def get_accelerator(keydefs, eventname):
1684 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001685 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1686 # if not keylist:
Ned Deily57847df2014-03-27 20:47:04 -07001687 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily60651532011-01-31 00:52:49 +00001688 "<<open-module>>",
1689 "<<goto-line>>",
1690 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001691 return ""
1692 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001693 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001694 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1695 s = re.sub("Key-", "", s)
1696 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1697 s = re.sub("Control-", "Ctrl-", s)
1698 s = re.sub("-", "+", s)
1699 s = re.sub("><", " ", s)
1700 s = re.sub("<", "", s)
1701 s = re.sub(">", "", s)
1702 return s
1703
1704
1705def fixwordbreaks(root):
1706 # Make sure that Tk's double-click and next/previous word
1707 # operations use our definition of a word (i.e. an identifier)
1708 tk = root.tk
1709 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1710 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1711 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1712
1713
Terry Jan Reedy43458462014-05-19 00:12:00 -04001714def _editor_window(parent):
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001715 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001716 fixwordbreaks(root)
Terry Jan Reedy43458462014-05-19 00:12:00 -04001717## root.withdraw()
David Scherer7aced172000-08-15 01:13:23 +00001718 if sys.argv[1:]:
1719 filename = sys.argv[1]
1720 else:
1721 filename = None
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001722 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001723 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedy43458462014-05-19 00:12:00 -04001724## edit.set_close_hook(root.quit)
1725## edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001726
1727if __name__ == '__main__':
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001728 from idlelib.idle_test.htest import run
1729 if len(sys.argv) <= 1:
Terry Jan Reedy43458462014-05-19 00:12:00 -04001730 run(_help_dialog)
1731 run(_editor_window)