blob: 94cf31429dd7be422235e18fb8d45fd0e53df64a [file] [log] [blame]
Brett Cannonaef82d32012-04-14 20:44:23 -04001import importlib
Brett Cannon50793b42013-06-07 13:17:48 -04002import importlib.abc
Eric Snow6029e082014-01-25 15:32:46 -07003import importlib.util
David Scherer7aced172000-08-15 01:13:23 +00004import os
Terry Jan Reedy94338de2014-01-23 00:36:46 -05005from platform import python_version
David Scherer7aced172000-08-15 01:13:23 +00006import re
Guido van Rossum33d26892007-08-05 15:29:28 +00007import string
Brett Cannonaef82d32012-04-14 20:44:23 -04008import sys
Georg Brandl14fc4272008-05-17 18:39:55 +00009from tkinter import *
10import tkinter.simpledialog as tkSimpleDialog
11import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000012import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000013import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000014
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000015from idlelib.MultiCall import MultiCallCreator
16from idlelib import idlever
17from idlelib import WindowList
18from idlelib import SearchDialog
19from idlelib import GrepDialog
20from idlelib import ReplaceDialog
21from idlelib import PyParse
22from idlelib.configHandler import idleConf
23from idlelib import aboutDialog, textView, configDialog
24from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000025
26# The default tab setting for a Text widget, in average-width characters.
27TK_TABWIDTH_DEFAULT = 8
28
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000029def _sphinx_version():
30 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
31 major, minor, micro, level, serial = sys.version_info
32 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020033 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000034 if level == 'candidate':
35 release += 'rc%s' % (serial,)
36 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000037 release += '%s%s' % (level[0], serial)
38 return release
39
Terry Jan Reedye91e7632012-02-05 15:14:20 -050040
41class HelpDialog(object):
42
43 def __init__(self):
44 self.parent = None # parent of help window
45 self.dlg = None # the help window iteself
46
47 def display(self, parent, near=None):
48 """ Display the help dialog.
49
50 parent - parent widget for the help window
51
52 near - a Toplevel widget (e.g. EditorWindow or PyShell)
53 to use as a reference for placing the help window
54 """
55 if self.dlg is None:
56 self.show_dialog(parent)
57 if near:
58 self.nearwindow(near)
59
60 def show_dialog(self, parent):
61 self.parent = parent
62 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
63 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
64 dlg.bind('<Destroy>', self.destroy, '+')
65
66 def nearwindow(self, near):
67 # Place the help dialog near the window specified by parent.
68 # Note - this may not reposition the window in Metacity
69 # if "/apps/metacity/general/disable_workarounds" is enabled
70 dlg = self.dlg
71 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
72 dlg.withdraw()
73 dlg.geometry("=+%d+%d" % geom)
74 dlg.deiconify()
75 dlg.lift()
76
77 def destroy(self, ev=None):
78 self.dlg = None
79 self.parent = None
80
81helpDialog = HelpDialog() # singleton instance
Terry Jan Reedyab4fd442014-05-19 00:12:10 -040082def _help_dialog(parent): # wrapper for htest
Terry Jan Reedy06313b72014-05-11 23:32:32 -040083 helpDialog.show_dialog(parent)
Terry Jan Reedye91e7632012-02-05 15:14:20 -050084
85
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000086class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000087 from idlelib.Percolator import Percolator
88 from idlelib.ColorDelegator import ColorDelegator
89 from idlelib.UndoDelegator import UndoDelegator
90 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
91 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000092 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000093 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000094
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000095 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000096
97 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000098 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +010099 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000100 if sys.platform.count('linux'):
101 # look for html docs in a couple of standard places
102 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
103 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
104 dochome = '/var/www/html/python/index.html'
105 else:
106 basepath = '/usr/share/doc/' # standard location
107 dochome = os.path.join(basepath, pyver,
108 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000109 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100110 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +0000111 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000112 if os.path.isfile(chmfile):
113 dochome = chmfile
Ned Deilyb7601672014-03-27 20:49:14 -0700114 elif sys.platform == 'darwin':
115 # documentation may be stored inside a python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100116 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000117 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000118 dochome = os.path.normpath(dochome)
119 if os.path.isfile(dochome):
120 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000121 if sys.platform == 'darwin':
122 # Safari requires real file:-URLs
123 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000124 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +0000125 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000126 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000127 self.flist = flist
128 root = root or flist.root
129 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000130 try:
131 sys.ps1
132 except AttributeError:
133 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000134 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000135 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000136 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000137 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200138 #self.top.instance_dict makes flist.inversedict available to
139 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000140 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000141 else:
142 self.tkinter_vars = {} # keys: Tkinter event names
143 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000144 self.top.instance_dict = {}
145 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000146 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000147 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000148 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200149 self.width = idleConf.GetOption('main', 'EditorWindow',
150 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000151 text_options = {
152 'name': 'text',
153 'padx': 5,
154 'wrap': 'none',
155 'width': self.width,
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200156 'height': idleConf.GetOption('main', 'EditorWindow',
157 'height', type='int')}
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000158 if TkVersion >= 8.5:
159 # Starting with tk 8.5 we have to set the new tabstyle option
160 # to 'wordprocessor' to achieve the same display of tabs as in
161 # older tk versions.
162 text_options['tabstyle'] = 'wordprocessor'
163 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000164 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000165
166 self.createmenubar()
167 self.apply_bindings()
168
169 self.top.protocol("WM_DELETE_WINDOW", self.close)
170 self.top.bind("<<close-window>>", self.close_event)
Ned Deilyb7601672014-03-27 20:49:14 -0700171 if macosxSupport.isAquaTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000172 # Command-W on editorwindows doesn't work without this.
173 text.bind('<<close-window>>', self.close_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000174 # Some OS X systems have only one mouse button,
175 # so use control-click for pulldown menus there.
176 # (Note, AquaTk defines <2> as the right button if
177 # present and the Tk Text widget already binds <2>.)
178 text.bind("<Control-Button-1>",self.right_menu_event)
179 else:
180 # Elsewhere, use right-click for pulldown menus.
181 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000182 text.bind("<<cut>>", self.cut)
183 text.bind("<<copy>>", self.copy)
184 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000185 text.bind("<<center-insert>>", self.center_insert_event)
186 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000187 text.bind("<<python-docs>>", self.python_docs)
188 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000189 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000190 text.bind("<<open-module>>", self.open_module)
191 text.bind("<<do-nothing>>", lambda event: "break")
192 text.bind("<<select-all>>", self.select_all)
193 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000194 text.bind("<<find>>", self.find_event)
195 text.bind("<<find-again>>", self.find_again_event)
196 text.bind("<<find-in-files>>", self.find_in_files_event)
197 text.bind("<<find-selection>>", self.find_selection_event)
198 text.bind("<<replace>>", self.replace_event)
199 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000200 text.bind("<<smart-backspace>>",self.smart_backspace_event)
201 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
202 text.bind("<<smart-indent>>",self.smart_indent_event)
203 text.bind("<<indent-region>>",self.indent_region_event)
204 text.bind("<<dedent-region>>",self.dedent_region_event)
205 text.bind("<<comment-region>>",self.comment_region_event)
206 text.bind("<<uncomment-region>>",self.uncomment_region_event)
207 text.bind("<<tabify-region>>",self.tabify_region_event)
208 text.bind("<<untabify-region>>",self.untabify_region_event)
209 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
210 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000211 text.bind("<Left>", self.move_at_edge_if_selection(0))
212 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000213 text.bind("<<del-word-left>>", self.del_word_left)
214 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000215 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000216
David Scherer7aced172000-08-15 01:13:23 +0000217 if flist:
218 flist.inversedict[self] = key
219 if key:
220 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000221 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000222 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
223 text.bind("<<open-class-browser>>", self.open_class_browser)
224 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400225 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000226
Steven M. Gava898a3652001-10-07 11:10:44 +0000227 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000228 vbar['command'] = text.yview
229 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000230 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000231 fontWeight = 'normal'
232 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000233 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000234 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200235 idleConf.GetOption('main', 'EditorWindow',
236 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000237 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000238 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
239 text.pack(side=TOP, fill=BOTH, expand=1)
240 text.focus_set()
241
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000242 # usetabs true -> literal tab characters are used by indent and
243 # dedent cmds, possibly mixed with spaces if
244 # indentwidth is not a multiple of tabwidth,
245 # which will cause Tabnanny to nag!
246 # false -> tab characters are converted to spaces by indent
247 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000248 # Although use-spaces=0 can be configured manually in config-main.def,
249 # configuration of tabs v. spaces is not supported in the configuration
250 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200251 usespaces = idleConf.GetOption('main', 'Indent',
252 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000253 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000254
255 # tabwidth is the display width of a literal tab character.
256 # CAUTION: telling Tk to use anything other than its default
257 # tab setting causes it to use an entirely different tabbing algorithm,
258 # treating tab stops as fixed distances from the left margin.
259 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000260 self.tabwidth = 8 # must remain 8 until Tk is fixed.
261
262 # indentwidth is the number of screen characters per indent level.
263 # The recommended Python indentation is four spaces.
264 self.indentwidth = self.tabwidth
265 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000266
267 # If context_use_ps1 is true, parsing searches back for a ps1 line;
268 # else searches for a popular (if, def, ...) Python stmt.
269 self.context_use_ps1 = False
270
271 # When searching backwards for a reliable place to begin parsing,
272 # first start num_context_lines[0] lines back, then
273 # num_context_lines[1] lines back if that didn't work, and so on.
274 # The last value should be huge (larger than the # of lines in a
275 # conceivable file).
276 # Making the initial values larger slows things down more often.
277 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000278 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000279 self.undo = undo = self.UndoDelegator()
280 per.insertfilter(undo)
281 text.undo_block_start = undo.undo_block_start
282 text.undo_block_stop = undo.undo_block_stop
283 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000284 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000285 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000286 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000287 self.good_load = False
288 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000289 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000290 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000291 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000292 if io.loadfile(filename):
293 self.good_load = True
294 is_py_src = self.ispythonsource(filename)
295 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000296 else:
297 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500298 self.good_load = True
299
Christian Heimesa156e092008-02-16 07:38:31 +0000300 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000301 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000302 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000303 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000304 menu = self.menudict.get('windows')
305 if menu:
306 end = menu.index("end")
307 if end is None:
308 end = -1
309 if end >= 0:
310 menu.add_separator()
311 end = end + 1
312 self.wmenu_end = end
313 WindowList.register_callback(self.postwindowsmenu)
314
315 # Some abstractions so IDLE extensions are cross-IDE
316 self.askyesno = tkMessageBox.askyesno
317 self.askinteger = tkSimpleDialog.askinteger
318 self.showerror = tkMessageBox.showerror
319
Roger Serwycaf30242013-05-20 22:13:39 -0500320 self._highlight_workaround() # Fix selection tags on Windows
321
322 def _highlight_workaround(self):
323 # On Windows, Tk removes painting of the selection
324 # tags which is different behavior than on Linux and Mac.
325 # See issue14146 for more information.
326 if not sys.platform.startswith('win'):
327 return
328
329 text = self.text
330 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
331 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
332 def highlight_fix(focus):
333 sel_range = text.tag_ranges("sel")
334 if sel_range:
335 if focus == 'out':
336 HILITE_CONFIG = idleConf.GetHighlight(
337 idleConf.CurrentTheme(), 'hilite')
338 text.tag_config("sel_fix", HILITE_CONFIG)
339 text.tag_raise("sel_fix")
340 text.tag_add("sel_fix", *sel_range)
341 elif focus == 'in':
342 text.tag_remove("sel_fix", "1.0", "end")
343
344 text.bind("<<Highlight-FocusOut>>",
345 lambda ev: highlight_fix("out"))
346 text.bind("<<Highlight-FocusIn>>",
347 lambda ev: highlight_fix("in"))
348
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"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000352 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000353 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
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000370 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400371 if (event.state & 4) != 0 and event.keysym == "Home":
372 # state&4==Control. If <Control-Home>, use the Tk binding.
373 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000374 if self.text.index("iomark") and \
375 self.text.compare("iomark", "<=", "insert lineend") and \
376 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400377 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000378 insertpt = int(self.text.index("iomark").split(".")[1])
379 else:
380 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000381 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000382 if line[insertpt] not in (' ','\t'):
383 break
384 else:
385 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000386 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000387 if insertpt == lineat:
388 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000389 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000390 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400391 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000392 self.text.tag_remove("sel", "1.0", "end")
393 else:
394 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200395 # there was no previous selection
396 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400397 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200398 if self.text.compare(self.text.index("sel.first"), "<",
399 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400400 self.text.mark_set("my_anchor", "sel.first") # extend back
401 else:
402 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000403 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400404 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000405 if self.text.compare(first,">",last):
406 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000407 self.text.tag_remove("sel", "1.0", "end")
408 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000409 self.text.mark_set("insert", dest)
410 self.text.see("insert")
411 return "break"
412
David Scherer7aced172000-08-15 01:13:23 +0000413 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000414 self.status_bar = self.MultiStatusBar(self.top)
Ned Deilyb7601672014-03-27 20:49:14 -0700415 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000416 # Insert some padding to avoid obscuring some of the statusbar
417 # by the resize widget.
418 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000419 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
420 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
421 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000422 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
423 self.text.event_add("<<set-line-and-column>>",
424 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000425 self.text.after_idle(self.set_line_and_column)
426
427 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000428 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000429 self.status_bar.set_label('column', 'Col: %s' % column)
430 self.status_bar.set_label('line', 'Ln: %s' % line)
431
David Scherer7aced172000-08-15 01:13:23 +0000432 menu_specs = [
433 ("file", "_File"),
434 ("edit", "_Edit"),
435 ("format", "F_ormat"),
436 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000437 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000438 ("windows", "_Windows"),
439 ("help", "_Help"),
440 ]
441
Ned Deilyb7601672014-03-27 20:49:14 -0700442 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000443 menu_specs[-2] = ("windows", "_Window")
444
445
David Scherer7aced172000-08-15 01:13:23 +0000446 def createmenubar(self):
447 mbar = self.menubar
448 self.menudict = menudict = {}
449 for name, label in self.menu_specs:
450 underline, label = prepstr(label)
451 menudict[name] = menu = Menu(mbar, name=name)
452 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ned Deilyb7601672014-03-27 20:49:14 -0700453 if macosxSupport.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000454 # Insert the application menu
455 menudict['application'] = menu = Menu(mbar, name='apple')
456 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000457 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000458 self.recent_files_menu = Menu(self.menubar)
459 self.menudict['file'].insert_cascade(3, label='Recent Files',
460 underline=0,
461 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000462 self.base_helpmenu_length = self.menudict['help'].index(END)
463 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000464
465 def postwindowsmenu(self):
466 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000467 menu = self.menudict['windows']
468 end = menu.index("end")
469 if end is None:
470 end = -1
471 if end > self.wmenu_end:
472 menu.delete(self.wmenu_end+1, end)
473 WindowList.add_windows_to_menu(menu)
474
475 rmenu = None
476
477 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000478 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
479 if not self.rmenu:
480 self.make_rmenu()
481 rmenu = self.rmenu
482 self.event = event
483 iswin = sys.platform[:3] == 'win'
484 if iswin:
485 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200486
Roger Serwy6b2918a2013-04-07 12:15:52 -0500487 for item in self.rmenu_specs:
488 try:
489 label, eventname, verify_state = item
490 except ValueError: # see issue1207589
491 continue
492
Andrew Svetlovd1837672012-11-01 22:41:19 +0200493 if verify_state is None:
494 continue
495 state = getattr(self, verify_state)()
496 rmenu.entryconfigure(label, state=state)
497
498
David Scherer7aced172000-08-15 01:13:23 +0000499 rmenu.tk_popup(event.x_root, event.y_root)
500 if iswin:
501 self.text.config(cursor="ibeam")
502
503 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200504 # ("Label", "<<virtual-event>>", "statefuncname"), ...
505 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000506 ]
507
508 def make_rmenu(self):
509 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500510 for item in self.rmenu_specs:
511 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200512 if label is not None:
513 def command(text=self.text, eventname=eventname):
514 text.event_generate(eventname)
515 rmenu.add_command(label=label, command=command)
516 else:
517 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000518 self.rmenu = rmenu
519
Andrew Svetlovd1837672012-11-01 22:41:19 +0200520 def rmenu_check_cut(self):
521 return self.rmenu_check_copy()
522
523 def rmenu_check_copy(self):
524 try:
525 indx = self.text.index('sel.first')
526 except TclError:
527 return 'disabled'
528 else:
529 return 'normal' if indx else 'disabled'
530
531 def rmenu_check_paste(self):
532 try:
533 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
534 except TclError:
535 return 'disabled'
536 else:
537 return 'normal'
538
David Scherer7aced172000-08-15 01:13:23 +0000539 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000540 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000541
Steven M. Gava3b55a892001-11-21 05:56:26 +0000542 def config_dialog(self, event=None):
543 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000544
David Scherer7aced172000-08-15 01:13:23 +0000545 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500546 if self.root:
547 parent = self.root
548 else:
549 parent = self.top
550 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000551
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000552 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000553 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000554 try:
555 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200556 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000557 tkMessageBox.showerror(title='Document Start Failure',
558 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000559 else:
560 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000561 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000562
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000563 def cut(self,event):
564 self.text.event_generate("<<Cut>>")
565 return "break"
566
567 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000568 if not self.text.tag_ranges("sel"):
569 # There is no selection, so do nothing and maybe interrupt.
570 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000571 self.text.event_generate("<<Copy>>")
572 return "break"
573
574 def paste(self,event):
575 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000576 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000577 return "break"
578
David Scherer7aced172000-08-15 01:13:23 +0000579 def select_all(self, event=None):
580 self.text.tag_add("sel", "1.0", "end-1c")
581 self.text.mark_set("insert", "1.0")
582 self.text.see("insert")
583 return "break"
584
585 def remove_selection(self, event=None):
586 self.text.tag_remove("sel", "1.0", "end")
587 self.text.see("insert")
588
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000589 def move_at_edge_if_selection(self, edge_index):
590 """Cursor move begins at start or end of selection
591
592 When a left/right cursor key is pressed create and return to Tkinter a
593 function which causes a cursor move from the associated edge of the
594 selection.
595
596 """
597 self_text_index = self.text.index
598 self_text_mark_set = self.text.mark_set
599 edges_table = ("sel.first+1c", "sel.last-1c")
600 def move_at_edge(event):
601 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
602 try:
603 self_text_index("sel.first")
604 self_text_mark_set("insert", edges_table[edge_index])
605 except TclError:
606 pass
607 return move_at_edge
608
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000609 def del_word_left(self, event):
610 self.text.event_generate('<Meta-Delete>')
611 return "break"
612
613 def del_word_right(self, event):
614 self.text.event_generate('<Meta-d>')
615 return "break"
616
Steven M. Gavac5976402002-01-04 03:06:08 +0000617 def find_event(self, event):
618 SearchDialog.find(self.text)
619 return "break"
620
621 def find_again_event(self, event):
622 SearchDialog.find_again(self.text)
623 return "break"
624
625 def find_selection_event(self, event):
626 SearchDialog.find_selection(self.text)
627 return "break"
628
629 def find_in_files_event(self, event):
630 GrepDialog.grep(self.text, self.io, self.flist)
631 return "break"
632
633 def replace_event(self, event):
634 ReplaceDialog.replace(self.text)
635 return "break"
636
637 def goto_line_event(self, event):
638 text = self.text
639 lineno = tkSimpleDialog.askinteger("Goto",
640 "Go to line number:",parent=text)
641 if lineno is None:
642 return "break"
643 if lineno <= 0:
644 text.bell()
645 return "break"
646 text.mark_set("insert", "%d.0" % lineno)
647 text.see("insert")
648
David Scherer7aced172000-08-15 01:13:23 +0000649 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000650 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000651 try:
652 name = self.text.get("sel.first", "sel.last")
653 except TclError:
654 name = ""
655 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000656 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000657 name = tkSimpleDialog.askstring("Module",
658 "Enter the name of a Python module\n"
659 "to search on sys.path and open:",
660 parent=self.text, initialvalue=name)
661 if name:
662 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000663 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000664 return
David Scherer7aced172000-08-15 01:13:23 +0000665 # XXX Ought to insert current file's directory in front of path
666 try:
Eric Snow6029e082014-01-25 15:32:46 -0700667 spec = importlib.util.find_spec(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400668 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000669 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
670 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700671 if spec is None:
Brett Cannon50793b42013-06-07 13:17:48 -0400672 tkMessageBox.showerror("Import error", "module not found",
673 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000674 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700675 if not isinstance(spec.loader, importlib.abc.SourceLoader):
Brett Cannon50793b42013-06-07 13:17:48 -0400676 tkMessageBox.showerror("Import error", "not a source-based module",
677 parent=self.text)
678 return
679 try:
Eric Snow02b9f9d2014-01-06 20:42:59 -0700680 file_path = spec.loader.get_filename(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400681 except AttributeError:
682 tkMessageBox.showerror("Import error",
683 "loader does not support get_filename",
684 parent=self.text)
685 return
David Scherer7aced172000-08-15 01:13:23 +0000686 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400687 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000688 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400689 self.io.loadfile(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000690
691 def open_class_browser(self, event=None):
692 filename = self.io.filename
693 if not filename:
694 tkMessageBox.showerror(
695 "No filename",
696 "This buffer has no associated filename",
697 master=self.text)
698 self.text.focus_set()
699 return None
700 head, tail = os.path.split(filename)
701 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000702 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000703 ClassBrowser.ClassBrowser(self.flist, base, [head])
704
705 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000706 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000707 PathBrowser.PathBrowser(self.flist)
708
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400709 def open_turtle_demo(self, event = None):
710 import subprocess
711
712 cmd = [sys.executable,
713 '-c',
714 'from turtledemo.__main__ import main; main()']
715 p = subprocess.Popen(cmd, shell=False)
716
David Scherer7aced172000-08-15 01:13:23 +0000717 def gotoline(self, lineno):
718 if lineno is not None and lineno > 0:
719 self.text.mark_set("insert", "%d.0" % lineno)
720 self.text.tag_remove("sel", "1.0", "end")
721 self.text.tag_add("sel", "insert", "insert +1l")
722 self.center()
723
724 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000725 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000726 return True
David Scherer7aced172000-08-15 01:13:23 +0000727 base, ext = os.path.splitext(os.path.basename(filename))
728 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000729 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000730 line = self.text.get('1.0', '1.0 lineend')
731 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000732
733 def close_hook(self):
734 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000735 self.flist.unregister_maybe_terminate(self)
736 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000737
738 def set_close_hook(self, close_hook):
739 self.close_hook = close_hook
740
741 def filename_change_hook(self):
742 if self.flist:
743 self.flist.filename_changed_edit(self)
744 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000745 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000746 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000747
Christian Heimesa156e092008-02-16 07:38:31 +0000748 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000749 if self.color:
750 return
Christian Heimesa156e092008-02-16 07:38:31 +0000751 if self.ispythonsource(self.io.filename):
752 self.color = self.ColorDelegator()
753 # can add more colorizers here...
754 if self.color:
755 self.per.removefilter(self.undo)
756 self.per.insertfilter(self.color)
757 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000758
Christian Heimesa156e092008-02-16 07:38:31 +0000759 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000760 if not self.color:
761 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000762 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000763 self.per.removefilter(self.color)
764 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000765
Steven M. Gavab77d3432002-03-02 07:16:21 +0000766 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000767 "Update the colour theme"
768 # Called from self.filename_change_hook and from configDialog.py
769 self._rmcolorizer()
770 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000771 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000772 normal_colors = idleConf.GetHighlight(theme, 'normal')
773 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
774 select_colors = idleConf.GetHighlight(theme, 'hilite')
775 self.text.config(
776 foreground=normal_colors['foreground'],
777 background=normal_colors['background'],
778 insertbackground=cursor_color,
779 selectforeground=select_colors['foreground'],
780 selectbackground=select_colors['background'],
781 )
David Scherer7aced172000-08-15 01:13:23 +0000782
Guido van Rossum33d26892007-08-05 15:29:28 +0000783 IDENTCHARS = string.ascii_letters + string.digits + "_"
784
785 def colorize_syntax_error(self, text, pos):
786 text.tag_add("ERROR", pos)
787 char = text.get(pos)
788 if char and char in self.IDENTCHARS:
789 text.tag_add("ERROR", pos + " wordstart", pos)
790 if '\n' == text.get(pos): # error at line end
791 text.mark_set("insert", pos)
792 else:
793 text.mark_set("insert", pos + "+1c")
794 text.see(pos)
795
Steven M. Gavab1585412002-03-12 00:21:56 +0000796 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000797 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000798 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000799 fontWeight='normal'
800 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
801 fontWeight='bold'
802 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200803 idleConf.GetOption('main','EditorWindow','font-size',
804 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000805 fontWeight))
806
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000807 def RemoveKeybindings(self):
808 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000809 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000810 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000811 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000812 self.text.event_delete(event, *keylist)
813 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000814 xkeydefs = idleConf.GetExtensionBindings(extensionName)
815 if xkeydefs:
816 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000817 self.text.event_delete(event, *keylist)
818
819 def ApplyKeybindings(self):
820 "Update the keybindings after they are changed"
821 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000822 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000823 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000824 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000825 xkeydefs = idleConf.GetExtensionBindings(extensionName)
826 if xkeydefs:
827 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000828 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000829 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000830 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000831 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000832 for item in menu[1]:
833 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000834 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000835 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000836 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700837 end = menu.index(END)
838 if end is None:
839 # Skip empty menus
840 continue
841 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000842 for index in range(0, end):
843 if menu.type(index) == 'command':
844 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000845 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000846 itemName = menu.entrycget(index, 'label')
847 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000848 if menubarItem in menuEventDict:
849 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000850 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000851 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000852 accel = get_accelerator(keydefs, event)
853 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000854
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000855 def set_notabs_indentwidth(self):
856 "Update the indentwidth if changed and not using tabs in this window"
857 # Called from configDialog.py
858 if not self.usetabs:
859 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
860 type='int')
861
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000862 def reset_help_menu_entries(self):
863 "Update the additional help entries on the Help menu"
864 help_list = idleConf.GetAllExtraHelpSourcesList()
865 helpmenu = self.menudict['help']
866 # first delete the extra help entries, if any
867 helpmenu_length = helpmenu.index(END)
868 if helpmenu_length > self.base_helpmenu_length:
869 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
870 # then rebuild them
871 if help_list:
872 helpmenu.add_separator()
873 for entry in help_list:
874 cmd = self.__extra_help_callback(entry[1])
875 helpmenu.add_command(label=entry[0], command=cmd)
876 # and update the menu dictionary
877 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000878
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000879 def __extra_help_callback(self, helpfile):
880 "Create a callback with the helpfile value frozen at definition time"
881 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000882 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000883 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000884 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000885 try:
886 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200887 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000888 tkMessageBox.showerror(title='Document Start Failure',
889 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000890 else:
891 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000892 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000893
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000894 def update_recent_files_list(self, new_file=None):
895 "Load and update the recent files list and menus"
896 rf_list = []
897 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400898 with open(self.recent_files_path, 'r',
899 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000900 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000901 if new_file:
902 new_file = os.path.abspath(new_file) + '\n'
903 if new_file in rf_list:
904 rf_list.remove(new_file) # move to top
905 rf_list.insert(0, new_file)
906 # clean and save the recent files list
907 bad_paths = []
908 for path in rf_list:
909 if '\0' in path or not os.path.exists(path[0:-1]):
910 bad_paths.append(path)
911 rf_list = [path for path in rf_list if path not in bad_paths]
912 ulchars = "1234567890ABCDEFGHIJK"
913 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000914 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800915 with open(self.recent_files_path, 'w',
916 encoding='utf_8', errors='replace') as rf_file:
917 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200918 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800919 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
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000926 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000927 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700928 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +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 Reedy94338de2014-01-23 00:36:46 -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 Reedy94338de2014-01-23 00:36:46 -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 Reedy94338de2014-01-23 00:36:46 -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)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001013 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001014
David Scherer7aced172000-08-15 01:13:23 +00001015 def close_event(self, event):
1016 self.close()
1017
1018 def maybesave(self):
1019 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001020 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001021 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001022 self.top.deiconify()
1023 self.top.lower()
1024 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001025 return self.io.maybesave()
1026
1027 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001028 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001029 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001030 self._close()
1031 return reply
1032
1033 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001034 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001035 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001036 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001037 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001038 self.io.close()
1039 self.io = None
1040 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001041 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001042 self.color.close(False)
1043 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001044 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001045 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001046 self.per.close()
1047 self.per = None
1048 self.top.destroy()
1049 if self.close_hook:
1050 # unless override: unregister from flist, terminate if last window
1051 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001052
1053 def load_extensions(self):
1054 self.extensions = {}
1055 self.load_standard_extensions()
1056
1057 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001058 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001059 if hasattr(ins, "close"):
1060 ins.close()
1061 self.extensions = {}
1062
1063 def load_standard_extensions(self):
1064 for name in self.get_standard_extension_names():
1065 try:
1066 self.load_extension(name)
1067 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001068 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001069 traceback.print_exc()
1070
1071 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001072 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001073
1074 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001075 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001076 try:
1077 mod = importlib.import_module('.' + name, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001078 except (ImportError, TypeError):
Brett Cannonaef82d32012-04-14 20:44:23 -04001079 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001080 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001081 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001082 raise
David Scherer7aced172000-08-15 01:13:23 +00001083 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001084 keydefs = idleConf.GetExtensionBindings(name)
1085 if hasattr(cls, "menudefs"):
1086 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001087 ins = cls(self)
1088 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001089 if keydefs:
1090 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001091 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001092 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001093 while methodname[:1] == '<':
1094 methodname = methodname[1:]
1095 while methodname[-1:] == '>':
1096 methodname = methodname[:-1]
1097 methodname = methodname + "_event"
1098 if hasattr(ins, methodname):
1099 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001100
1101 def apply_bindings(self, keydefs=None):
1102 if keydefs is None:
1103 keydefs = self.Bindings.default_keydefs
1104 text = self.text
1105 text.keydefs = keydefs
1106 for event, keylist in keydefs.items():
1107 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001108 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001109
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001110 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001111 """Add appropriate entries to the menus and submenus
1112
1113 Menus that are absent or None in self.menudict are ignored.
1114 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001115 if menudefs is None:
1116 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001117 if keydefs is None:
1118 keydefs = self.Bindings.default_keydefs
1119 menudict = self.menudict
1120 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001121 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001122 menu = menudict.get(mname)
1123 if not menu:
1124 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001125 for entry in entrylist:
1126 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001127 menu.add_separator()
1128 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001129 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001130 checkbutton = (label[:1] == '!')
1131 if checkbutton:
1132 label = label[1:]
1133 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001134 accelerator = get_accelerator(keydefs, eventname)
1135 def command(text=text, eventname=eventname):
1136 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001137 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001138 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001139 menu.add_checkbutton(label=label, underline=underline,
1140 command=command, accelerator=accelerator,
1141 variable=var)
1142 else:
1143 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001144 command=command,
1145 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001146
1147 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001148 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001149 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001150 value = var.get()
1151 return value
1152 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001153 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001154
1155 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001156 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001157 if var:
1158 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001159 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001160 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001161
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001162 def get_var_obj(self, name, vartype=None):
1163 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001164 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001165 # create a Tkinter variable object with self.text as master:
1166 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001167 return var
1168
1169 # Tk implementations of "virtual text methods" -- each platform
1170 # reusing IDLE's support code needs to define these for its GUI's
1171 # flavor of widget.
1172
1173 # Is character at text_index in a Python string? Return 0 for
1174 # "guaranteed no", true for anything else. This info is expensive
1175 # to compute ab initio, but is probably already known by the
1176 # platform's colorizer.
1177
1178 def is_char_in_string(self, text_index):
1179 if self.color:
1180 # Return true iff colorizer hasn't (re)gotten this far
1181 # yet, or the character is tagged as being in a string
1182 return self.text.tag_prevrange("TODO", text_index) or \
1183 "STRING" in self.text.tag_names(text_index)
1184 else:
1185 # The colorizer is missing: assume the worst
1186 return 1
1187
1188 # If a selection is defined in the text widget, return (start,
1189 # end) as Tkinter text indices, otherwise return (None, None)
1190 def get_selection_indices(self):
1191 try:
1192 first = self.text.index("sel.first")
1193 last = self.text.index("sel.last")
1194 return first, last
1195 except TclError:
1196 return None, None
1197
1198 # Return the text widget's current view of what a tab stop means
1199 # (equivalent width in spaces).
1200
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001201 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001202 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1203 return int(current)
1204
1205 # Set the text widget's current view of what a tab stop means.
1206
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001207 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001208 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001209 if self.get_tk_tabwidth() != newtabwidth:
1210 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001211 pixels = text.tk.call("font", "measure", text["font"],
1212 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001213 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001214 text.configure(tabs=pixels)
1215
Guido van Rossum33d26892007-08-05 15:29:28 +00001216### begin autoindent code ### (configuration was moved to beginning of class)
1217
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001218 def set_indentation_params(self, is_py_src, guess=True):
1219 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001220 i = self.guess_indent()
1221 if 2 <= i <= 8:
1222 self.indentwidth = i
1223 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001224 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001225 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001226
1227 def smart_backspace_event(self, event):
1228 text = self.text
1229 first, last = self.get_selection_indices()
1230 if first and last:
1231 text.delete(first, last)
1232 text.mark_set("insert", first)
1233 return "break"
1234 # Delete whitespace left, until hitting a real char or closest
1235 # preceding virtual tab stop.
1236 chars = text.get("insert linestart", "insert")
1237 if chars == '':
1238 if text.compare("insert", ">", "1.0"):
1239 # easy: delete preceding newline
1240 text.delete("insert-1c")
1241 else:
1242 text.bell() # at start of buffer
1243 return "break"
1244 if chars[-1] not in " \t":
1245 # easy: delete preceding real char
1246 text.delete("insert-1c")
1247 return "break"
1248 # Ick. It may require *inserting* spaces if we back up over a
1249 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001250 tabwidth = self.tabwidth
1251 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001252 assert have > 0
1253 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001254 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001255 if self.context_use_ps1:
1256 last_line_of_prompt = sys.ps1.split('\n')[-1]
1257 else:
1258 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001259 ncharsdeleted = 0
1260 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001261 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001262 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001263 chars = chars[:-1]
1264 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001265 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001266 if have <= want or chars[-1] not in " \t":
1267 break
1268 text.undo_block_start()
1269 text.delete("insert-%dc" % ncharsdeleted, "insert")
1270 if have < want:
1271 text.insert("insert", ' ' * (want - have))
1272 text.undo_block_stop()
1273 return "break"
1274
1275 def smart_indent_event(self, event):
1276 # if intraline selection:
1277 # delete it
1278 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001279 # do indent-region
1280 # else:
1281 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001282 text = self.text
1283 first, last = self.get_selection_indices()
1284 text.undo_block_start()
1285 try:
1286 if first and last:
1287 if index2line(first) != index2line(last):
1288 return self.indent_region_event(event)
1289 text.delete(first, last)
1290 text.mark_set("insert", first)
1291 prefix = text.get("insert linestart", "insert")
1292 raw, effective = classifyws(prefix, self.tabwidth)
1293 if raw == len(prefix):
1294 # only whitespace to the left
1295 self.reindent_to(effective + self.indentwidth)
1296 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001297 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001298 if self.usetabs:
1299 pad = '\t'
1300 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001301 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001302 n = self.indentwidth
1303 pad = ' ' * (n - effective % n)
1304 text.insert("insert", pad)
1305 text.see("insert")
1306 return "break"
1307 finally:
1308 text.undo_block_stop()
1309
1310 def newline_and_indent_event(self, event):
1311 text = self.text
1312 first, last = self.get_selection_indices()
1313 text.undo_block_start()
1314 try:
1315 if first and last:
1316 text.delete(first, last)
1317 text.mark_set("insert", first)
1318 line = text.get("insert linestart", "insert")
1319 i, n = 0, len(line)
1320 while i < n and line[i] in " \t":
1321 i = i+1
1322 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001323 # the cursor is in or at leading indentation in a continuation
1324 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001325 text.insert("insert linestart", '\n')
1326 return "break"
1327 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001328 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001329 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001330 last_line_of_prompt = sys.ps1.split('\n')[-1]
1331 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001332 line = line[:-1]
1333 i = i+1
1334 if i:
1335 text.delete("insert - %d chars" % i, "insert")
1336 # strip whitespace after insert point
1337 while text.get("insert") in " \t":
1338 text.delete("insert")
1339 # start new line
1340 text.insert("insert", '\n')
1341
1342 # adjust indentation for continuations and block
1343 # open/close first need to find the last stmt
1344 lno = index2line(text.index('insert'))
1345 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001346 if not self.context_use_ps1:
1347 for context in self.num_context_lines:
1348 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001349 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001350 rawtext = text.get(startatindex, "insert")
1351 y.set_str(rawtext)
1352 bod = y.find_good_parse_start(
1353 self.context_use_ps1,
1354 self._build_char_in_string_func(startatindex))
1355 if bod is not None or startat == 1:
1356 break
1357 y.set_lo(bod or 0)
1358 else:
1359 r = text.tag_prevrange("console", "insert")
1360 if r:
1361 startatindex = r[1]
1362 else:
1363 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001364 rawtext = text.get(startatindex, "insert")
1365 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001366 y.set_lo(0)
1367
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001368 c = y.get_continuation_type()
1369 if c != PyParse.C_NONE:
1370 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001371 if c == PyParse.C_STRING_FIRST_LINE:
1372 # after the first line of a string; do not indent at all
1373 pass
1374 elif c == PyParse.C_STRING_NEXT_LINES:
1375 # inside a string which started before this line;
1376 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001377 text.insert("insert", indent)
1378 elif c == PyParse.C_BRACKET:
1379 # line up with the first (if any) element of the
1380 # last open bracket structure; else indent one
1381 # level beyond the indent of the line with the
1382 # last open bracket
1383 self.reindent_to(y.compute_bracket_indent())
1384 elif c == PyParse.C_BACKSLASH:
1385 # if more than one line in this stmt already, just
1386 # mimic the current indent; else if initial line
1387 # has a start on an assignment stmt, indent to
1388 # beyond leftmost =; else to beyond first chunk of
1389 # non-whitespace on initial line
1390 if y.get_num_lines_in_stmt() > 1:
1391 text.insert("insert", indent)
1392 else:
1393 self.reindent_to(y.compute_backslash_indent())
1394 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001395 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001396 return "break"
1397
1398 # This line starts a brand new stmt; indent relative to
1399 # indentation of initial line of closest preceding
1400 # interesting stmt.
1401 indent = y.get_base_indent_string()
1402 text.insert("insert", indent)
1403 if y.is_block_opener():
1404 self.smart_indent_event(event)
1405 elif indent and y.is_block_closer():
1406 self.smart_backspace_event(event)
1407 return "break"
1408 finally:
1409 text.see("insert")
1410 text.undo_block_stop()
1411
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001412 # Our editwin provides a is_char_in_string function that works
1413 # with a Tk text index, but PyParse only knows about offsets into
1414 # a string. This builds a function for PyParse that accepts an
1415 # offset.
1416
1417 def _build_char_in_string_func(self, startindex):
1418 def inner(offset, _startindex=startindex,
1419 _icis=self.is_char_in_string):
1420 return _icis(_startindex + "+%dc" % offset)
1421 return inner
1422
1423 def indent_region_event(self, event):
1424 head, tail, chars, lines = self.get_region()
1425 for pos in range(len(lines)):
1426 line = lines[pos]
1427 if line:
1428 raw, effective = classifyws(line, self.tabwidth)
1429 effective = effective + self.indentwidth
1430 lines[pos] = self._make_blanks(effective) + line[raw:]
1431 self.set_region(head, tail, chars, lines)
1432 return "break"
1433
1434 def dedent_region_event(self, event):
1435 head, tail, chars, lines = self.get_region()
1436 for pos in range(len(lines)):
1437 line = lines[pos]
1438 if line:
1439 raw, effective = classifyws(line, self.tabwidth)
1440 effective = max(effective - self.indentwidth, 0)
1441 lines[pos] = self._make_blanks(effective) + line[raw:]
1442 self.set_region(head, tail, chars, lines)
1443 return "break"
1444
1445 def comment_region_event(self, event):
1446 head, tail, chars, lines = self.get_region()
1447 for pos in range(len(lines) - 1):
1448 line = lines[pos]
1449 lines[pos] = '##' + line
1450 self.set_region(head, tail, chars, lines)
1451
1452 def uncomment_region_event(self, event):
1453 head, tail, chars, lines = self.get_region()
1454 for pos in range(len(lines)):
1455 line = lines[pos]
1456 if not line:
1457 continue
1458 if line[:2] == '##':
1459 line = line[2:]
1460 elif line[:1] == '#':
1461 line = line[1:]
1462 lines[pos] = line
1463 self.set_region(head, tail, chars, lines)
1464
1465 def tabify_region_event(self, event):
1466 head, tail, chars, lines = self.get_region()
1467 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001468 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001469 for pos in range(len(lines)):
1470 line = lines[pos]
1471 if line:
1472 raw, effective = classifyws(line, tabwidth)
1473 ntabs, nspaces = divmod(effective, tabwidth)
1474 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1475 self.set_region(head, tail, chars, lines)
1476
1477 def untabify_region_event(self, event):
1478 head, tail, chars, lines = self.get_region()
1479 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001480 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001481 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001482 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001483 self.set_region(head, tail, chars, lines)
1484
1485 def toggle_tabs_event(self, event):
1486 if self.askyesno(
1487 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001488 "Turn tabs " + ("on", "off")[self.usetabs] +
1489 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001490 ("will be", "remains at")[self.usetabs] + " 8." +
1491 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001492 parent=self.text):
1493 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001494 # Try to prevent inconsistent indentation.
1495 # User must change indent width manually after using tabs.
1496 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001497 return "break"
1498
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001499 # XXX this isn't bound to anything -- see tabwidth comments
1500## def change_tabwidth_event(self, event):
1501## new = self._asktabwidth()
1502## if new != self.tabwidth:
1503## self.tabwidth = new
1504## self.set_indentation_params(0, guess=0)
1505## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001506
1507 def change_indentwidth_event(self, event):
1508 new = self.askinteger(
1509 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001510 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001511 parent=self.text,
1512 initialvalue=self.indentwidth,
1513 minvalue=2,
1514 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001515 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001516 self.indentwidth = new
1517 return "break"
1518
1519 def get_region(self):
1520 text = self.text
1521 first, last = self.get_selection_indices()
1522 if first and last:
1523 head = text.index(first + " linestart")
1524 tail = text.index(last + "-1c lineend +1c")
1525 else:
1526 head = text.index("insert linestart")
1527 tail = text.index("insert lineend +1c")
1528 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001529 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001530 return head, tail, chars, lines
1531
1532 def set_region(self, head, tail, chars, lines):
1533 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001534 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001535 if newchars == chars:
1536 text.bell()
1537 return
1538 text.tag_remove("sel", "1.0", "end")
1539 text.mark_set("insert", head)
1540 text.undo_block_start()
1541 text.delete(head, tail)
1542 text.insert(head, newchars)
1543 text.undo_block_stop()
1544 text.tag_add("sel", head, "insert")
1545
1546 # Make string that displays as n leading blanks.
1547
1548 def _make_blanks(self, n):
1549 if self.usetabs:
1550 ntabs, nspaces = divmod(n, self.tabwidth)
1551 return '\t' * ntabs + ' ' * nspaces
1552 else:
1553 return ' ' * n
1554
1555 # Delete from beginning of line to insert point, then reinsert
1556 # column logical (meaning use tabs if appropriate) spaces.
1557
1558 def reindent_to(self, column):
1559 text = self.text
1560 text.undo_block_start()
1561 if text.compare("insert linestart", "!=", "insert"):
1562 text.delete("insert linestart", "insert")
1563 if column:
1564 text.insert("insert", self._make_blanks(column))
1565 text.undo_block_stop()
1566
1567 def _asktabwidth(self):
1568 return self.askinteger(
1569 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001570 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001571 parent=self.text,
1572 initialvalue=self.indentwidth,
1573 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001574 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001575
1576 # Guess indentwidth from text content.
1577 # Return guessed indentwidth. This should not be believed unless
1578 # it's in a reasonable range (e.g., it will be 0 if no indented
1579 # blocks are found).
1580
1581 def guess_indent(self):
1582 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1583 if opener and indented:
1584 raw, indentsmall = classifyws(opener, self.tabwidth)
1585 raw, indentlarge = classifyws(indented, self.tabwidth)
1586 else:
1587 indentsmall = indentlarge = 0
1588 return indentlarge - indentsmall
1589
1590# "line.col" -> line, as an int
1591def index2line(index):
1592 return int(float(index))
1593
1594# Look at the leading whitespace in s.
1595# Return pair (# of leading ws characters,
1596# effective # of leading blanks after expanding
1597# tabs to width tabwidth)
1598
1599def classifyws(s, tabwidth):
1600 raw = effective = 0
1601 for ch in s:
1602 if ch == ' ':
1603 raw = raw + 1
1604 effective = effective + 1
1605 elif ch == '\t':
1606 raw = raw + 1
1607 effective = (effective // tabwidth + 1) * tabwidth
1608 else:
1609 break
1610 return raw, effective
1611
1612import tokenize
1613_tokenize = tokenize
1614del tokenize
1615
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001616class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001617
1618 # .run() chews over the Text widget, looking for a block opener
1619 # and the stmt following it. Returns a pair,
1620 # (line containing block opener, line containing stmt)
1621 # Either or both may be None.
1622
1623 def __init__(self, text, tabwidth):
1624 self.text = text
1625 self.tabwidth = tabwidth
1626 self.i = self.finished = 0
1627 self.blkopenline = self.indentedline = None
1628
1629 def readline(self):
1630 if self.finished:
1631 return ""
1632 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001633 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001634 if self.text.compare(mark, ">=", "end"):
1635 return ""
1636 return self.text.get(mark, mark + " lineend+1c")
1637
1638 def tokeneater(self, type, token, start, end, line,
1639 INDENT=_tokenize.INDENT,
1640 NAME=_tokenize.NAME,
1641 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1642 if self.finished:
1643 pass
1644 elif type == NAME and token in OPENERS:
1645 self.blkopenline = line
1646 elif type == INDENT and self.blkopenline:
1647 self.indentedline = line
1648 self.finished = 1
1649
1650 def run(self):
1651 save_tabsize = _tokenize.tabsize
1652 _tokenize.tabsize = self.tabwidth
1653 try:
1654 try:
Trent Nelson428de652008-03-18 22:41:35 +00001655 tokens = _tokenize.generate_tokens(self.readline)
1656 for token in tokens:
1657 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +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 Deily70063932011-01-29 18:29:01 +00001685 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1686 # if not keylist:
Ned Deilyb7601672014-03-27 20:49:14 -07001687 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +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 Reedyab4fd442014-05-19 00:12:10 -04001714def _editor_window(parent):
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001715 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001716 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001717 if sys.argv[1:]:
1718 filename = sys.argv[1]
1719 else:
1720 filename = None
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001721 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001722 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001723 edit.text.bind("<<close-all-windows>>", edit.close_event)
1724 parent.mainloop()
David Scherer7aced172000-08-15 01:13:23 +00001725
1726if __name__ == '__main__':
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001727 from idlelib.idle_test.htest import run
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001728 run(_help_dialog, _editor_window)