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