blob: eaf68cb7541190b441c55b54d06248423619a3b4 [file] [log] [blame]
Brett Cannonaef82d32012-04-14 20:44:23 -04001import importlib
Brett Cannon50793b42013-06-07 13:17:48 -04002import importlib.abc
David Scherer7aced172000-08-15 01:13:23 +00003import os
David Scherer7aced172000-08-15 01:13:23 +00004import re
Guido van Rossum33d26892007-08-05 15:29:28 +00005import string
Brett Cannonaef82d32012-04-14 20:44:23 -04006import sys
Georg Brandl14fc4272008-05-17 18:39:55 +00007from tkinter import *
8import tkinter.simpledialog as tkSimpleDialog
9import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000010import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000011import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000012
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000013from idlelib.MultiCall import MultiCallCreator
14from idlelib import idlever
15from idlelib import WindowList
16from idlelib import SearchDialog
17from idlelib import GrepDialog
18from idlelib import ReplaceDialog
19from idlelib import PyParse
20from idlelib.configHandler import idleConf
21from idlelib import aboutDialog, textView, configDialog
22from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000023
24# The default tab setting for a Text widget, in average-width characters.
25TK_TABWIDTH_DEFAULT = 8
26
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +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)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020031 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000032 if level == 'candidate':
33 release += 'rc%s' % (serial,)
34 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000035 release += '%s%s' % (level[0], serial)
36 return release
37
Terry Jan Reedye91e7632012-02-05 15:14:20 -050038
39class HelpDialog(object):
40
41 def __init__(self):
42 self.parent = None # parent of help window
43 self.dlg = None # the help window iteself
44
45 def display(self, parent, near=None):
46 """ Display the help dialog.
47
48 parent - parent widget for the help window
49
50 near - a Toplevel widget (e.g. EditorWindow or PyShell)
51 to use as a reference for placing the help window
52 """
53 if self.dlg is None:
54 self.show_dialog(parent)
55 if near:
56 self.nearwindow(near)
57
58 def show_dialog(self, parent):
59 self.parent = parent
60 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
61 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
62 dlg.bind('<Destroy>', self.destroy, '+')
63
64 def nearwindow(self, near):
65 # Place the help dialog near the window specified by parent.
66 # Note - this may not reposition the window in Metacity
67 # if "/apps/metacity/general/disable_workarounds" is enabled
68 dlg = self.dlg
69 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
70 dlg.withdraw()
71 dlg.geometry("=+%d+%d" % geom)
72 dlg.deiconify()
73 dlg.lift()
74
75 def destroy(self, ev=None):
76 self.dlg = None
77 self.parent = None
78
79helpDialog = HelpDialog() # singleton instance
80
81
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000082class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000083 from idlelib.Percolator import Percolator
84 from idlelib.ColorDelegator import ColorDelegator
85 from idlelib.UndoDelegator import UndoDelegator
86 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
87 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000088 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000089 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000090
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000091 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000092
93 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000094 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +010095 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000096 if sys.platform.count('linux'):
97 # look for html docs in a couple of standard places
98 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
99 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
100 dochome = '/var/www/html/python/index.html'
101 else:
102 basepath = '/usr/share/doc/' # standard location
103 dochome = os.path.join(basepath, pyver,
104 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000105 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100106 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +0000107 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000108 if os.path.isfile(chmfile):
109 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000110 elif macosxSupport.runningAsOSXApp():
111 # documentation is stored inside the python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100112 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000113 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000114 dochome = os.path.normpath(dochome)
115 if os.path.isfile(dochome):
116 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000117 if sys.platform == 'darwin':
118 # Safari requires real file:-URLs
119 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000120 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +0000121 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000122 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000123 self.flist = flist
124 root = root or flist.root
125 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000126 try:
127 sys.ps1
128 except AttributeError:
129 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000130 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000131 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000132 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000133 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200134 #self.top.instance_dict makes flist.inversedict available to
135 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000136 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000137 else:
138 self.tkinter_vars = {} # keys: Tkinter event names
139 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000140 self.top.instance_dict = {}
141 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000142 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000143 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000144 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200145 self.width = idleConf.GetOption('main', 'EditorWindow',
146 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000147 text_options = {
148 'name': 'text',
149 'padx': 5,
150 'wrap': 'none',
151 'width': self.width,
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200152 'height': idleConf.GetOption('main', 'EditorWindow',
153 'height', type='int')}
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000154 if TkVersion >= 8.5:
155 # Starting with tk 8.5 we have to set the new tabstyle option
156 # to 'wordprocessor' to achieve the same display of tabs as in
157 # older tk versions.
158 text_options['tabstyle'] = 'wordprocessor'
159 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000160 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000161
162 self.createmenubar()
163 self.apply_bindings()
164
165 self.top.protocol("WM_DELETE_WINDOW", self.close)
166 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000167 if macosxSupport.runningAsOSXApp():
168 # Command-W on editorwindows doesn't work without this.
169 text.bind('<<close-window>>', self.close_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000170 # Some OS X systems have only one mouse button,
171 # so use control-click for pulldown menus there.
172 # (Note, AquaTk defines <2> as the right button if
173 # present and the Tk Text widget already binds <2>.)
174 text.bind("<Control-Button-1>",self.right_menu_event)
175 else:
176 # Elsewhere, use right-click for pulldown menus.
177 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000178 text.bind("<<cut>>", self.cut)
179 text.bind("<<copy>>", self.copy)
180 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000181 text.bind("<<center-insert>>", self.center_insert_event)
182 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000183 text.bind("<<python-docs>>", self.python_docs)
184 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000185 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000186 text.bind("<<open-module>>", self.open_module)
187 text.bind("<<do-nothing>>", lambda event: "break")
188 text.bind("<<select-all>>", self.select_all)
189 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000190 text.bind("<<find>>", self.find_event)
191 text.bind("<<find-again>>", self.find_again_event)
192 text.bind("<<find-in-files>>", self.find_in_files_event)
193 text.bind("<<find-selection>>", self.find_selection_event)
194 text.bind("<<replace>>", self.replace_event)
195 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000196 text.bind("<<smart-backspace>>",self.smart_backspace_event)
197 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
198 text.bind("<<smart-indent>>",self.smart_indent_event)
199 text.bind("<<indent-region>>",self.indent_region_event)
200 text.bind("<<dedent-region>>",self.dedent_region_event)
201 text.bind("<<comment-region>>",self.comment_region_event)
202 text.bind("<<uncomment-region>>",self.uncomment_region_event)
203 text.bind("<<tabify-region>>",self.tabify_region_event)
204 text.bind("<<untabify-region>>",self.untabify_region_event)
205 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
206 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000207 text.bind("<Left>", self.move_at_edge_if_selection(0))
208 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000209 text.bind("<<del-word-left>>", self.del_word_left)
210 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000211 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000212
David Scherer7aced172000-08-15 01:13:23 +0000213 if flist:
214 flist.inversedict[self] = key
215 if key:
216 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000217 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000218 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
219 text.bind("<<open-class-browser>>", self.open_class_browser)
220 text.bind("<<open-path-browser>>", self.open_path_browser)
221
Steven M. Gava898a3652001-10-07 11:10:44 +0000222 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000223 vbar['command'] = text.yview
224 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000225 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000226 fontWeight = 'normal'
227 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000228 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000229 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200230 idleConf.GetOption('main', 'EditorWindow',
231 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000232 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000233 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
234 text.pack(side=TOP, fill=BOTH, expand=1)
235 text.focus_set()
236
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000237 # usetabs true -> literal tab characters are used by indent and
238 # dedent cmds, possibly mixed with spaces if
239 # indentwidth is not a multiple of tabwidth,
240 # which will cause Tabnanny to nag!
241 # false -> tab characters are converted to spaces by indent
242 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000243 # Although use-spaces=0 can be configured manually in config-main.def,
244 # configuration of tabs v. spaces is not supported in the configuration
245 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200246 usespaces = idleConf.GetOption('main', 'Indent',
247 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000248 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000249
250 # tabwidth is the display width of a literal tab character.
251 # CAUTION: telling Tk to use anything other than its default
252 # tab setting causes it to use an entirely different tabbing algorithm,
253 # treating tab stops as fixed distances from the left margin.
254 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000255 self.tabwidth = 8 # must remain 8 until Tk is fixed.
256
257 # indentwidth is the number of screen characters per indent level.
258 # The recommended Python indentation is four spaces.
259 self.indentwidth = self.tabwidth
260 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000261
262 # If context_use_ps1 is true, parsing searches back for a ps1 line;
263 # else searches for a popular (if, def, ...) Python stmt.
264 self.context_use_ps1 = False
265
266 # When searching backwards for a reliable place to begin parsing,
267 # first start num_context_lines[0] lines back, then
268 # num_context_lines[1] lines back if that didn't work, and so on.
269 # The last value should be huge (larger than the # of lines in a
270 # conceivable file).
271 # Making the initial values larger slows things down more often.
272 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000273 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000274 self.undo = undo = self.UndoDelegator()
275 per.insertfilter(undo)
276 text.undo_block_start = undo.undo_block_start
277 text.undo_block_stop = undo.undo_block_stop
278 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000279 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000280 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000281 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000282 self.good_load = False
283 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000284 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000285 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000286 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000287 if io.loadfile(filename):
288 self.good_load = True
289 is_py_src = self.ispythonsource(filename)
290 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000291 else:
292 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500293 self.good_load = True
294
Christian Heimesa156e092008-02-16 07:38:31 +0000295 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000296 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000297 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000298 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000299 menu = self.menudict.get('windows')
300 if menu:
301 end = menu.index("end")
302 if end is None:
303 end = -1
304 if end >= 0:
305 menu.add_separator()
306 end = end + 1
307 self.wmenu_end = end
308 WindowList.register_callback(self.postwindowsmenu)
309
310 # Some abstractions so IDLE extensions are cross-IDE
311 self.askyesno = tkMessageBox.askyesno
312 self.askinteger = tkSimpleDialog.askinteger
313 self.showerror = tkMessageBox.showerror
314
Roger Serwycaf30242013-05-20 22:13:39 -0500315 self._highlight_workaround() # Fix selection tags on Windows
316
317 def _highlight_workaround(self):
318 # On Windows, Tk removes painting of the selection
319 # tags which is different behavior than on Linux and Mac.
320 # See issue14146 for more information.
321 if not sys.platform.startswith('win'):
322 return
323
324 text = self.text
325 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
326 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
327 def highlight_fix(focus):
328 sel_range = text.tag_ranges("sel")
329 if sel_range:
330 if focus == 'out':
331 HILITE_CONFIG = idleConf.GetHighlight(
332 idleConf.CurrentTheme(), 'hilite')
333 text.tag_config("sel_fix", HILITE_CONFIG)
334 text.tag_raise("sel_fix")
335 text.tag_add("sel_fix", *sel_range)
336 elif focus == 'in':
337 text.tag_remove("sel_fix", "1.0", "end")
338
339 text.bind("<<Highlight-FocusOut>>",
340 lambda ev: highlight_fix("out"))
341 text.bind("<<Highlight-FocusIn>>",
342 lambda ev: highlight_fix("in"))
343
344
Martin v. Löwis307021f2005-11-27 16:59:04 +0000345 def _filename_to_unicode(self, filename):
346 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000347 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000348 return filename
349 else:
350 try:
351 return filename.decode(self.filesystemencoding)
352 except UnicodeDecodeError:
353 # XXX
354 try:
355 return filename.decode(self.encoding)
356 except UnicodeDecodeError:
357 # byte-to-byte conversion
358 return filename.decode('iso8859-1')
359
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000360 def new_callback(self, event):
361 dirname, basename = self.io.defaultfilename()
362 self.flist.new(dirname)
363 return "break"
364
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000365 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400366 if (event.state & 4) != 0 and event.keysym == "Home":
367 # state&4==Control. If <Control-Home>, use the Tk binding.
368 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000369 if self.text.index("iomark") and \
370 self.text.compare("iomark", "<=", "insert lineend") and \
371 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400372 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000373 insertpt = int(self.text.index("iomark").split(".")[1])
374 else:
375 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000376 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000377 if line[insertpt] not in (' ','\t'):
378 break
379 else:
380 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000381 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000382 if insertpt == lineat:
383 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000384 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000385 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400386 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000387 self.text.tag_remove("sel", "1.0", "end")
388 else:
389 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200390 # there was no previous selection
391 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400392 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200393 if self.text.compare(self.text.index("sel.first"), "<",
394 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400395 self.text.mark_set("my_anchor", "sel.first") # extend back
396 else:
397 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000398 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400399 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000400 if self.text.compare(first,">",last):
401 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000402 self.text.tag_remove("sel", "1.0", "end")
403 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000404 self.text.mark_set("insert", dest)
405 self.text.see("insert")
406 return "break"
407
David Scherer7aced172000-08-15 01:13:23 +0000408 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000409 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000410 if macosxSupport.runningAsOSXApp():
411 # Insert some padding to avoid obscuring some of the statusbar
412 # by the resize widget.
413 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000414 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
415 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
416 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000417 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
418 self.text.event_add("<<set-line-and-column>>",
419 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000420 self.text.after_idle(self.set_line_and_column)
421
422 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000423 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000424 self.status_bar.set_label('column', 'Col: %s' % column)
425 self.status_bar.set_label('line', 'Ln: %s' % line)
426
David Scherer7aced172000-08-15 01:13:23 +0000427 menu_specs = [
428 ("file", "_File"),
429 ("edit", "_Edit"),
430 ("format", "F_ormat"),
431 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000432 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000433 ("windows", "_Windows"),
434 ("help", "_Help"),
435 ]
436
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000437 if macosxSupport.runningAsOSXApp():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000438 menu_specs[-2] = ("windows", "_Window")
439
440
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)
446 menudict[name] = menu = Menu(mbar, name=name)
447 mbar.add_cascade(label=label, menu=menu, underline=underline)
Georg Brandlaedd2892010-12-19 10:10:32 +0000448 if macosxSupport.isCarbonAquaTk(self.root):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000449 # Insert the application menu
450 menudict['application'] = menu = Menu(mbar, name='apple')
451 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000452 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000453 self.recent_files_menu = Menu(self.menubar)
454 self.menudict['file'].insert_cascade(3, label='Recent Files',
455 underline=0,
456 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000457 self.base_helpmenu_length = self.menudict['help'].index(END)
458 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000459
460 def postwindowsmenu(self):
461 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000462 menu = self.menudict['windows']
463 end = menu.index("end")
464 if end is None:
465 end = -1
466 if end > self.wmenu_end:
467 menu.delete(self.wmenu_end+1, end)
468 WindowList.add_windows_to_menu(menu)
469
470 rmenu = None
471
472 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000473 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
474 if not self.rmenu:
475 self.make_rmenu()
476 rmenu = self.rmenu
477 self.event = event
478 iswin = sys.platform[:3] == 'win'
479 if iswin:
480 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200481
Roger Serwy6b2918a2013-04-07 12:15:52 -0500482 for item in self.rmenu_specs:
483 try:
484 label, eventname, verify_state = item
485 except ValueError: # see issue1207589
486 continue
487
Andrew Svetlovd1837672012-11-01 22:41:19 +0200488 if verify_state is None:
489 continue
490 state = getattr(self, verify_state)()
491 rmenu.entryconfigure(label, state=state)
492
493
David Scherer7aced172000-08-15 01:13:23 +0000494 rmenu.tk_popup(event.x_root, event.y_root)
495 if iswin:
496 self.text.config(cursor="ibeam")
497
498 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200499 # ("Label", "<<virtual-event>>", "statefuncname"), ...
500 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000501 ]
502
503 def make_rmenu(self):
504 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500505 for item in self.rmenu_specs:
506 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200507 if label is not None:
508 def command(text=self.text, eventname=eventname):
509 text.event_generate(eventname)
510 rmenu.add_command(label=label, command=command)
511 else:
512 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000513 self.rmenu = rmenu
514
Andrew Svetlovd1837672012-11-01 22:41:19 +0200515 def rmenu_check_cut(self):
516 return self.rmenu_check_copy()
517
518 def rmenu_check_copy(self):
519 try:
520 indx = self.text.index('sel.first')
521 except TclError:
522 return 'disabled'
523 else:
524 return 'normal' if indx else 'disabled'
525
526 def rmenu_check_paste(self):
527 try:
528 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
529 except TclError:
530 return 'disabled'
531 else:
532 return 'normal'
533
David Scherer7aced172000-08-15 01:13:23 +0000534 def about_dialog(self, event=None):
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):
538 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000539
David Scherer7aced172000-08-15 01:13:23 +0000540 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500541 if self.root:
542 parent = self.root
543 else:
544 parent = self.top
545 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000546
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000547 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000548 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000549 try:
550 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200551 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000552 tkMessageBox.showerror(title='Document Start Failure',
553 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000554 else:
555 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000556 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000557
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000558 def cut(self,event):
559 self.text.event_generate("<<Cut>>")
560 return "break"
561
562 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000563 if not self.text.tag_ranges("sel"):
564 # There is no selection, so do nothing and maybe interrupt.
565 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000566 self.text.event_generate("<<Copy>>")
567 return "break"
568
569 def paste(self,event):
570 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000571 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000572 return "break"
573
David Scherer7aced172000-08-15 01:13:23 +0000574 def select_all(self, event=None):
575 self.text.tag_add("sel", "1.0", "end-1c")
576 self.text.mark_set("insert", "1.0")
577 self.text.see("insert")
578 return "break"
579
580 def remove_selection(self, event=None):
581 self.text.tag_remove("sel", "1.0", "end")
582 self.text.see("insert")
583
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000584 def move_at_edge_if_selection(self, edge_index):
585 """Cursor move begins at start or end of selection
586
587 When a left/right cursor key is pressed create and return to Tkinter a
588 function which causes a cursor move from the associated edge of the
589 selection.
590
591 """
592 self_text_index = self.text.index
593 self_text_mark_set = self.text.mark_set
594 edges_table = ("sel.first+1c", "sel.last-1c")
595 def move_at_edge(event):
596 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
597 try:
598 self_text_index("sel.first")
599 self_text_mark_set("insert", edges_table[edge_index])
600 except TclError:
601 pass
602 return move_at_edge
603
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000604 def del_word_left(self, event):
605 self.text.event_generate('<Meta-Delete>')
606 return "break"
607
608 def del_word_right(self, event):
609 self.text.event_generate('<Meta-d>')
610 return "break"
611
Steven M. Gavac5976402002-01-04 03:06:08 +0000612 def find_event(self, event):
613 SearchDialog.find(self.text)
614 return "break"
615
616 def find_again_event(self, event):
617 SearchDialog.find_again(self.text)
618 return "break"
619
620 def find_selection_event(self, event):
621 SearchDialog.find_selection(self.text)
622 return "break"
623
624 def find_in_files_event(self, event):
625 GrepDialog.grep(self.text, self.io, self.flist)
626 return "break"
627
628 def replace_event(self, event):
629 ReplaceDialog.replace(self.text)
630 return "break"
631
632 def goto_line_event(self, event):
633 text = self.text
634 lineno = tkSimpleDialog.askinteger("Goto",
635 "Go to line number:",parent=text)
636 if lineno is None:
637 return "break"
638 if lineno <= 0:
639 text.bell()
640 return "break"
641 text.mark_set("insert", "%d.0" % lineno)
642 text.see("insert")
643
David Scherer7aced172000-08-15 01:13:23 +0000644 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000645 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000646 try:
647 name = self.text.get("sel.first", "sel.last")
648 except TclError:
649 name = ""
650 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000651 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000652 name = tkSimpleDialog.askstring("Module",
653 "Enter the name of a Python module\n"
654 "to search on sys.path and open:",
655 parent=self.text, initialvalue=name)
656 if name:
657 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000658 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000659 return
David Scherer7aced172000-08-15 01:13:23 +0000660 # XXX Ought to insert current file's directory in front of path
661 try:
Brett Cannon50793b42013-06-07 13:17:48 -0400662 loader = importlib.find_loader(name)
663 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000664 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
665 return
Brett Cannon50793b42013-06-07 13:17:48 -0400666 if loader is None:
667 tkMessageBox.showerror("Import error", "module not found",
668 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000669 return
Brett Cannon50793b42013-06-07 13:17:48 -0400670 if not isinstance(loader, importlib.abc.SourceLoader):
671 tkMessageBox.showerror("Import error", "not a source-based module",
672 parent=self.text)
673 return
674 try:
675 file_path = loader.get_filename(name)
676 except AttributeError:
677 tkMessageBox.showerror("Import error",
678 "loader does not support get_filename",
679 parent=self.text)
680 return
David Scherer7aced172000-08-15 01:13:23 +0000681 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400682 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000683 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400684 self.io.loadfile(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000685
686 def open_class_browser(self, event=None):
687 filename = self.io.filename
688 if not filename:
689 tkMessageBox.showerror(
690 "No filename",
691 "This buffer has no associated filename",
692 master=self.text)
693 self.text.focus_set()
694 return None
695 head, tail = os.path.split(filename)
696 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000697 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000698 ClassBrowser.ClassBrowser(self.flist, base, [head])
699
700 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000701 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000702 PathBrowser.PathBrowser(self.flist)
703
704 def gotoline(self, lineno):
705 if lineno is not None and lineno > 0:
706 self.text.mark_set("insert", "%d.0" % lineno)
707 self.text.tag_remove("sel", "1.0", "end")
708 self.text.tag_add("sel", "insert", "insert +1l")
709 self.center()
710
711 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000712 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000713 return True
David Scherer7aced172000-08-15 01:13:23 +0000714 base, ext = os.path.splitext(os.path.basename(filename))
715 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000716 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000717 line = self.text.get('1.0', '1.0 lineend')
718 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000719
720 def close_hook(self):
721 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000722 self.flist.unregister_maybe_terminate(self)
723 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000724
725 def set_close_hook(self, close_hook):
726 self.close_hook = close_hook
727
728 def filename_change_hook(self):
729 if self.flist:
730 self.flist.filename_changed_edit(self)
731 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000732 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000733 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000734
Christian Heimesa156e092008-02-16 07:38:31 +0000735 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000736 if self.color:
737 return
Christian Heimesa156e092008-02-16 07:38:31 +0000738 if self.ispythonsource(self.io.filename):
739 self.color = self.ColorDelegator()
740 # can add more colorizers here...
741 if self.color:
742 self.per.removefilter(self.undo)
743 self.per.insertfilter(self.color)
744 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000745
Christian Heimesa156e092008-02-16 07:38:31 +0000746 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000747 if not self.color:
748 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000749 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000750 self.per.removefilter(self.color)
751 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000752
Steven M. Gavab77d3432002-03-02 07:16:21 +0000753 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000754 "Update the colour theme"
755 # Called from self.filename_change_hook and from configDialog.py
756 self._rmcolorizer()
757 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000758 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000759 normal_colors = idleConf.GetHighlight(theme, 'normal')
760 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
761 select_colors = idleConf.GetHighlight(theme, 'hilite')
762 self.text.config(
763 foreground=normal_colors['foreground'],
764 background=normal_colors['background'],
765 insertbackground=cursor_color,
766 selectforeground=select_colors['foreground'],
767 selectbackground=select_colors['background'],
768 )
David Scherer7aced172000-08-15 01:13:23 +0000769
Guido van Rossum33d26892007-08-05 15:29:28 +0000770 IDENTCHARS = string.ascii_letters + string.digits + "_"
771
772 def colorize_syntax_error(self, text, pos):
773 text.tag_add("ERROR", pos)
774 char = text.get(pos)
775 if char and char in self.IDENTCHARS:
776 text.tag_add("ERROR", pos + " wordstart", pos)
777 if '\n' == text.get(pos): # error at line end
778 text.mark_set("insert", pos)
779 else:
780 text.mark_set("insert", pos + "+1c")
781 text.see(pos)
782
Steven M. Gavab1585412002-03-12 00:21:56 +0000783 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000784 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000785 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000786 fontWeight='normal'
787 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
788 fontWeight='bold'
789 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200790 idleConf.GetOption('main','EditorWindow','font-size',
791 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000792 fontWeight))
793
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000794 def RemoveKeybindings(self):
795 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000796 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000797 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000798 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000799 self.text.event_delete(event, *keylist)
800 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000801 xkeydefs = idleConf.GetExtensionBindings(extensionName)
802 if xkeydefs:
803 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000804 self.text.event_delete(event, *keylist)
805
806 def ApplyKeybindings(self):
807 "Update the keybindings after they are changed"
808 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000809 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000810 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000811 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000812 xkeydefs = idleConf.GetExtensionBindings(extensionName)
813 if xkeydefs:
814 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000815 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000816 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000817 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000818 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000819 for item in menu[1]:
820 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000821 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000822 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000823 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700824 end = menu.index(END)
825 if end is None:
826 # Skip empty menus
827 continue
828 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000829 for index in range(0, end):
830 if menu.type(index) == 'command':
831 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000832 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000833 itemName = menu.entrycget(index, 'label')
834 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000835 if menubarItem in menuEventDict:
836 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000837 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000838 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000839 accel = get_accelerator(keydefs, event)
840 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000841
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000842 def set_notabs_indentwidth(self):
843 "Update the indentwidth if changed and not using tabs in this window"
844 # Called from configDialog.py
845 if not self.usetabs:
846 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
847 type='int')
848
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000849 def reset_help_menu_entries(self):
850 "Update the additional help entries on the Help menu"
851 help_list = idleConf.GetAllExtraHelpSourcesList()
852 helpmenu = self.menudict['help']
853 # first delete the extra help entries, if any
854 helpmenu_length = helpmenu.index(END)
855 if helpmenu_length > self.base_helpmenu_length:
856 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
857 # then rebuild them
858 if help_list:
859 helpmenu.add_separator()
860 for entry in help_list:
861 cmd = self.__extra_help_callback(entry[1])
862 helpmenu.add_command(label=entry[0], command=cmd)
863 # and update the menu dictionary
864 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000865
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000866 def __extra_help_callback(self, helpfile):
867 "Create a callback with the helpfile value frozen at definition time"
868 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000869 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000870 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000871 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000872 try:
873 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200874 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000875 tkMessageBox.showerror(title='Document Start Failure',
876 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000877 else:
878 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000879 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000880
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000881 def update_recent_files_list(self, new_file=None):
882 "Load and update the recent files list and menus"
883 rf_list = []
884 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400885 with open(self.recent_files_path, 'r',
886 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000887 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000888 if new_file:
889 new_file = os.path.abspath(new_file) + '\n'
890 if new_file in rf_list:
891 rf_list.remove(new_file) # move to top
892 rf_list.insert(0, new_file)
893 # clean and save the recent files list
894 bad_paths = []
895 for path in rf_list:
896 if '\0' in path or not os.path.exists(path[0:-1]):
897 bad_paths.append(path)
898 rf_list = [path for path in rf_list if path not in bad_paths]
899 ulchars = "1234567890ABCDEFGHIJK"
900 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000901 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800902 with open(self.recent_files_path, 'w',
903 encoding='utf_8', errors='replace') as rf_file:
904 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200905 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800906 if not getattr(self.root, "recentfilelist_error_displayed", False):
907 self.root.recentfilelist_error_displayed = True
908 tkMessageBox.showerror(title='IDLE Error',
909 message='Unable to update Recent Files list:\n%s'
910 % str(err),
911 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000912 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000913 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000914 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700915 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000916 for i, file_name in enumerate(rf_list):
917 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000918 # make unicode string to display non-ASCII chars correctly
919 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000920 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000921 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000922 command=callback,
923 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000924
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000925 def __recent_file_callback(self, file_name):
926 def open_recent_file(fn_closure=file_name):
927 self.io.open(editFile=fn_closure)
928 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000929
David Scherer7aced172000-08-15 01:13:23 +0000930 def saved_change_hook(self):
931 short = self.short_title()
932 long = self.long_title()
933 if short and long:
934 title = short + " - " + long
935 elif short:
936 title = short
937 elif long:
938 title = long
939 else:
940 title = "Untitled"
941 icon = short or long or title
942 if not self.get_saved():
943 title = "*%s*" % title
944 icon = "*%s" % icon
945 self.top.wm_title(title)
946 self.top.wm_iconname(icon)
947
948 def get_saved(self):
949 return self.undo.get_saved()
950
951 def set_saved(self, flag):
952 self.undo.set_saved(flag)
953
954 def reset_undo(self):
955 self.undo.reset_undo()
956
957 def short_title(self):
958 filename = self.io.filename
959 if filename:
960 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000961 # return unicode string to display non-ASCII chars correctly
962 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000963
964 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000965 # return unicode string to display non-ASCII chars correctly
966 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000967
968 def center_insert_event(self, event):
969 self.center()
970
971 def center(self, mark="insert"):
972 text = self.text
973 top, bot = self.getwindowlines()
974 lineno = self.getlineno(mark)
975 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000976 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000977 text.yview(float(newtop))
978
979 def getwindowlines(self):
980 text = self.text
981 top = self.getlineno("@0,0")
982 bot = self.getlineno("@0,65535")
983 if top == bot and text.winfo_height() == 1:
984 # Geometry manager hasn't run yet
985 height = int(text['height'])
986 bot = top + height - 1
987 return top, bot
988
989 def getlineno(self, mark="insert"):
990 text = self.text
991 return int(float(text.index(mark)))
992
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000993 def get_geometry(self):
994 "Return (width, height, x, y)"
995 geom = self.top.wm_geometry()
996 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000997 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000998
David Scherer7aced172000-08-15 01:13:23 +0000999 def close_event(self, event):
1000 self.close()
1001
1002 def maybesave(self):
1003 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001004 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001005 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001006 self.top.deiconify()
1007 self.top.lower()
1008 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001009 return self.io.maybesave()
1010
1011 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001012 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001013 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001014 self._close()
1015 return reply
1016
1017 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001018 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001019 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001020 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001021 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001022 self.io.close()
1023 self.io = None
1024 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001025 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001026 self.color.close(False)
1027 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001028 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001029 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001030 self.per.close()
1031 self.per = None
1032 self.top.destroy()
1033 if self.close_hook:
1034 # unless override: unregister from flist, terminate if last window
1035 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001036
1037 def load_extensions(self):
1038 self.extensions = {}
1039 self.load_standard_extensions()
1040
1041 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001042 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001043 if hasattr(ins, "close"):
1044 ins.close()
1045 self.extensions = {}
1046
1047 def load_standard_extensions(self):
1048 for name in self.get_standard_extension_names():
1049 try:
1050 self.load_extension(name)
1051 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001052 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001053 traceback.print_exc()
1054
1055 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001056 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001057
1058 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001059 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001060 try:
1061 mod = importlib.import_module('.' + name, package=__package__)
1062 except ImportError:
1063 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001064 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001065 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001066 raise
David Scherer7aced172000-08-15 01:13:23 +00001067 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001068 keydefs = idleConf.GetExtensionBindings(name)
1069 if hasattr(cls, "menudefs"):
1070 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001071 ins = cls(self)
1072 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001073 if keydefs:
1074 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001075 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001076 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001077 while methodname[:1] == '<':
1078 methodname = methodname[1:]
1079 while methodname[-1:] == '>':
1080 methodname = methodname[:-1]
1081 methodname = methodname + "_event"
1082 if hasattr(ins, methodname):
1083 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001084
1085 def apply_bindings(self, keydefs=None):
1086 if keydefs is None:
1087 keydefs = self.Bindings.default_keydefs
1088 text = self.text
1089 text.keydefs = keydefs
1090 for event, keylist in keydefs.items():
1091 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001092 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001093
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001094 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001095 """Add appropriate entries to the menus and submenus
1096
1097 Menus that are absent or None in self.menudict are ignored.
1098 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001099 if menudefs is None:
1100 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001101 if keydefs is None:
1102 keydefs = self.Bindings.default_keydefs
1103 menudict = self.menudict
1104 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001105 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001106 menu = menudict.get(mname)
1107 if not menu:
1108 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001109 for entry in entrylist:
1110 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001111 menu.add_separator()
1112 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001113 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001114 checkbutton = (label[:1] == '!')
1115 if checkbutton:
1116 label = label[1:]
1117 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001118 accelerator = get_accelerator(keydefs, eventname)
1119 def command(text=text, eventname=eventname):
1120 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001121 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001122 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001123 menu.add_checkbutton(label=label, underline=underline,
1124 command=command, accelerator=accelerator,
1125 variable=var)
1126 else:
1127 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001128 command=command,
1129 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001130
1131 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001132 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001133 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001134 value = var.get()
1135 return value
1136 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001137 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001138
1139 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001140 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001141 if var:
1142 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001143 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001144 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001145
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001146 def get_var_obj(self, name, vartype=None):
1147 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001148 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001149 # create a Tkinter variable object with self.text as master:
1150 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001151 return var
1152
1153 # Tk implementations of "virtual text methods" -- each platform
1154 # reusing IDLE's support code needs to define these for its GUI's
1155 # flavor of widget.
1156
1157 # Is character at text_index in a Python string? Return 0 for
1158 # "guaranteed no", true for anything else. This info is expensive
1159 # to compute ab initio, but is probably already known by the
1160 # platform's colorizer.
1161
1162 def is_char_in_string(self, text_index):
1163 if self.color:
1164 # Return true iff colorizer hasn't (re)gotten this far
1165 # yet, or the character is tagged as being in a string
1166 return self.text.tag_prevrange("TODO", text_index) or \
1167 "STRING" in self.text.tag_names(text_index)
1168 else:
1169 # The colorizer is missing: assume the worst
1170 return 1
1171
1172 # If a selection is defined in the text widget, return (start,
1173 # end) as Tkinter text indices, otherwise return (None, None)
1174 def get_selection_indices(self):
1175 try:
1176 first = self.text.index("sel.first")
1177 last = self.text.index("sel.last")
1178 return first, last
1179 except TclError:
1180 return None, None
1181
1182 # Return the text widget's current view of what a tab stop means
1183 # (equivalent width in spaces).
1184
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001185 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001186 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1187 return int(current)
1188
1189 # Set the text widget's current view of what a tab stop means.
1190
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001191 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001192 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001193 if self.get_tk_tabwidth() != newtabwidth:
1194 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001195 pixels = text.tk.call("font", "measure", text["font"],
1196 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001197 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001198 text.configure(tabs=pixels)
1199
Guido van Rossum33d26892007-08-05 15:29:28 +00001200### begin autoindent code ### (configuration was moved to beginning of class)
1201
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001202 def set_indentation_params(self, is_py_src, guess=True):
1203 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001204 i = self.guess_indent()
1205 if 2 <= i <= 8:
1206 self.indentwidth = i
1207 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001208 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001209 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001210
1211 def smart_backspace_event(self, event):
1212 text = self.text
1213 first, last = self.get_selection_indices()
1214 if first and last:
1215 text.delete(first, last)
1216 text.mark_set("insert", first)
1217 return "break"
1218 # Delete whitespace left, until hitting a real char or closest
1219 # preceding virtual tab stop.
1220 chars = text.get("insert linestart", "insert")
1221 if chars == '':
1222 if text.compare("insert", ">", "1.0"):
1223 # easy: delete preceding newline
1224 text.delete("insert-1c")
1225 else:
1226 text.bell() # at start of buffer
1227 return "break"
1228 if chars[-1] not in " \t":
1229 # easy: delete preceding real char
1230 text.delete("insert-1c")
1231 return "break"
1232 # Ick. It may require *inserting* spaces if we back up over a
1233 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001234 tabwidth = self.tabwidth
1235 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001236 assert have > 0
1237 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001238 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001239 if self.context_use_ps1:
1240 last_line_of_prompt = sys.ps1.split('\n')[-1]
1241 else:
1242 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001243 ncharsdeleted = 0
1244 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001245 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001246 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001247 chars = chars[:-1]
1248 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001249 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001250 if have <= want or chars[-1] not in " \t":
1251 break
1252 text.undo_block_start()
1253 text.delete("insert-%dc" % ncharsdeleted, "insert")
1254 if have < want:
1255 text.insert("insert", ' ' * (want - have))
1256 text.undo_block_stop()
1257 return "break"
1258
1259 def smart_indent_event(self, event):
1260 # if intraline selection:
1261 # delete it
1262 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001263 # do indent-region
1264 # else:
1265 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001266 text = self.text
1267 first, last = self.get_selection_indices()
1268 text.undo_block_start()
1269 try:
1270 if first and last:
1271 if index2line(first) != index2line(last):
1272 return self.indent_region_event(event)
1273 text.delete(first, last)
1274 text.mark_set("insert", first)
1275 prefix = text.get("insert linestart", "insert")
1276 raw, effective = classifyws(prefix, self.tabwidth)
1277 if raw == len(prefix):
1278 # only whitespace to the left
1279 self.reindent_to(effective + self.indentwidth)
1280 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001281 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001282 if self.usetabs:
1283 pad = '\t'
1284 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001285 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001286 n = self.indentwidth
1287 pad = ' ' * (n - effective % n)
1288 text.insert("insert", pad)
1289 text.see("insert")
1290 return "break"
1291 finally:
1292 text.undo_block_stop()
1293
1294 def newline_and_indent_event(self, event):
1295 text = self.text
1296 first, last = self.get_selection_indices()
1297 text.undo_block_start()
1298 try:
1299 if first and last:
1300 text.delete(first, last)
1301 text.mark_set("insert", first)
1302 line = text.get("insert linestart", "insert")
1303 i, n = 0, len(line)
1304 while i < n and line[i] in " \t":
1305 i = i+1
1306 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001307 # the cursor is in or at leading indentation in a continuation
1308 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001309 text.insert("insert linestart", '\n')
1310 return "break"
1311 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001312 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001313 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001314 last_line_of_prompt = sys.ps1.split('\n')[-1]
1315 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001316 line = line[:-1]
1317 i = i+1
1318 if i:
1319 text.delete("insert - %d chars" % i, "insert")
1320 # strip whitespace after insert point
1321 while text.get("insert") in " \t":
1322 text.delete("insert")
1323 # start new line
1324 text.insert("insert", '\n')
1325
1326 # adjust indentation for continuations and block
1327 # open/close first need to find the last stmt
1328 lno = index2line(text.index('insert'))
1329 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001330 if not self.context_use_ps1:
1331 for context in self.num_context_lines:
1332 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001333 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001334 rawtext = text.get(startatindex, "insert")
1335 y.set_str(rawtext)
1336 bod = y.find_good_parse_start(
1337 self.context_use_ps1,
1338 self._build_char_in_string_func(startatindex))
1339 if bod is not None or startat == 1:
1340 break
1341 y.set_lo(bod or 0)
1342 else:
1343 r = text.tag_prevrange("console", "insert")
1344 if r:
1345 startatindex = r[1]
1346 else:
1347 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001348 rawtext = text.get(startatindex, "insert")
1349 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001350 y.set_lo(0)
1351
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001352 c = y.get_continuation_type()
1353 if c != PyParse.C_NONE:
1354 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001355 if c == PyParse.C_STRING_FIRST_LINE:
1356 # after the first line of a string; do not indent at all
1357 pass
1358 elif c == PyParse.C_STRING_NEXT_LINES:
1359 # inside a string which started before this line;
1360 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001361 text.insert("insert", indent)
1362 elif c == PyParse.C_BRACKET:
1363 # line up with the first (if any) element of the
1364 # last open bracket structure; else indent one
1365 # level beyond the indent of the line with the
1366 # last open bracket
1367 self.reindent_to(y.compute_bracket_indent())
1368 elif c == PyParse.C_BACKSLASH:
1369 # if more than one line in this stmt already, just
1370 # mimic the current indent; else if initial line
1371 # has a start on an assignment stmt, indent to
1372 # beyond leftmost =; else to beyond first chunk of
1373 # non-whitespace on initial line
1374 if y.get_num_lines_in_stmt() > 1:
1375 text.insert("insert", indent)
1376 else:
1377 self.reindent_to(y.compute_backslash_indent())
1378 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001379 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001380 return "break"
1381
1382 # This line starts a brand new stmt; indent relative to
1383 # indentation of initial line of closest preceding
1384 # interesting stmt.
1385 indent = y.get_base_indent_string()
1386 text.insert("insert", indent)
1387 if y.is_block_opener():
1388 self.smart_indent_event(event)
1389 elif indent and y.is_block_closer():
1390 self.smart_backspace_event(event)
1391 return "break"
1392 finally:
1393 text.see("insert")
1394 text.undo_block_stop()
1395
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001396 # Our editwin provides a is_char_in_string function that works
1397 # with a Tk text index, but PyParse only knows about offsets into
1398 # a string. This builds a function for PyParse that accepts an
1399 # offset.
1400
1401 def _build_char_in_string_func(self, startindex):
1402 def inner(offset, _startindex=startindex,
1403 _icis=self.is_char_in_string):
1404 return _icis(_startindex + "+%dc" % offset)
1405 return inner
1406
1407 def indent_region_event(self, event):
1408 head, tail, chars, lines = self.get_region()
1409 for pos in range(len(lines)):
1410 line = lines[pos]
1411 if line:
1412 raw, effective = classifyws(line, self.tabwidth)
1413 effective = effective + self.indentwidth
1414 lines[pos] = self._make_blanks(effective) + line[raw:]
1415 self.set_region(head, tail, chars, lines)
1416 return "break"
1417
1418 def dedent_region_event(self, event):
1419 head, tail, chars, lines = self.get_region()
1420 for pos in range(len(lines)):
1421 line = lines[pos]
1422 if line:
1423 raw, effective = classifyws(line, self.tabwidth)
1424 effective = max(effective - self.indentwidth, 0)
1425 lines[pos] = self._make_blanks(effective) + line[raw:]
1426 self.set_region(head, tail, chars, lines)
1427 return "break"
1428
1429 def comment_region_event(self, event):
1430 head, tail, chars, lines = self.get_region()
1431 for pos in range(len(lines) - 1):
1432 line = lines[pos]
1433 lines[pos] = '##' + line
1434 self.set_region(head, tail, chars, lines)
1435
1436 def uncomment_region_event(self, event):
1437 head, tail, chars, lines = self.get_region()
1438 for pos in range(len(lines)):
1439 line = lines[pos]
1440 if not line:
1441 continue
1442 if line[:2] == '##':
1443 line = line[2:]
1444 elif line[:1] == '#':
1445 line = line[1:]
1446 lines[pos] = line
1447 self.set_region(head, tail, chars, lines)
1448
1449 def tabify_region_event(self, event):
1450 head, tail, chars, lines = self.get_region()
1451 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001452 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001453 for pos in range(len(lines)):
1454 line = lines[pos]
1455 if line:
1456 raw, effective = classifyws(line, tabwidth)
1457 ntabs, nspaces = divmod(effective, tabwidth)
1458 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1459 self.set_region(head, tail, chars, lines)
1460
1461 def untabify_region_event(self, event):
1462 head, tail, chars, lines = self.get_region()
1463 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001464 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001465 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001466 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001467 self.set_region(head, tail, chars, lines)
1468
1469 def toggle_tabs_event(self, event):
1470 if self.askyesno(
1471 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001472 "Turn tabs " + ("on", "off")[self.usetabs] +
1473 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001474 ("will be", "remains at")[self.usetabs] + " 8." +
1475 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001476 parent=self.text):
1477 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001478 # Try to prevent inconsistent indentation.
1479 # User must change indent width manually after using tabs.
1480 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001481 return "break"
1482
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001483 # XXX this isn't bound to anything -- see tabwidth comments
1484## def change_tabwidth_event(self, event):
1485## new = self._asktabwidth()
1486## if new != self.tabwidth:
1487## self.tabwidth = new
1488## self.set_indentation_params(0, guess=0)
1489## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001490
1491 def change_indentwidth_event(self, event):
1492 new = self.askinteger(
1493 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001494 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001495 parent=self.text,
1496 initialvalue=self.indentwidth,
1497 minvalue=2,
1498 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001499 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001500 self.indentwidth = new
1501 return "break"
1502
1503 def get_region(self):
1504 text = self.text
1505 first, last = self.get_selection_indices()
1506 if first and last:
1507 head = text.index(first + " linestart")
1508 tail = text.index(last + "-1c lineend +1c")
1509 else:
1510 head = text.index("insert linestart")
1511 tail = text.index("insert lineend +1c")
1512 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001513 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001514 return head, tail, chars, lines
1515
1516 def set_region(self, head, tail, chars, lines):
1517 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001518 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001519 if newchars == chars:
1520 text.bell()
1521 return
1522 text.tag_remove("sel", "1.0", "end")
1523 text.mark_set("insert", head)
1524 text.undo_block_start()
1525 text.delete(head, tail)
1526 text.insert(head, newchars)
1527 text.undo_block_stop()
1528 text.tag_add("sel", head, "insert")
1529
1530 # Make string that displays as n leading blanks.
1531
1532 def _make_blanks(self, n):
1533 if self.usetabs:
1534 ntabs, nspaces = divmod(n, self.tabwidth)
1535 return '\t' * ntabs + ' ' * nspaces
1536 else:
1537 return ' ' * n
1538
1539 # Delete from beginning of line to insert point, then reinsert
1540 # column logical (meaning use tabs if appropriate) spaces.
1541
1542 def reindent_to(self, column):
1543 text = self.text
1544 text.undo_block_start()
1545 if text.compare("insert linestart", "!=", "insert"):
1546 text.delete("insert linestart", "insert")
1547 if column:
1548 text.insert("insert", self._make_blanks(column))
1549 text.undo_block_stop()
1550
1551 def _asktabwidth(self):
1552 return self.askinteger(
1553 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001554 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001555 parent=self.text,
1556 initialvalue=self.indentwidth,
1557 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001558 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001559
1560 # Guess indentwidth from text content.
1561 # Return guessed indentwidth. This should not be believed unless
1562 # it's in a reasonable range (e.g., it will be 0 if no indented
1563 # blocks are found).
1564
1565 def guess_indent(self):
1566 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1567 if opener and indented:
1568 raw, indentsmall = classifyws(opener, self.tabwidth)
1569 raw, indentlarge = classifyws(indented, self.tabwidth)
1570 else:
1571 indentsmall = indentlarge = 0
1572 return indentlarge - indentsmall
1573
1574# "line.col" -> line, as an int
1575def index2line(index):
1576 return int(float(index))
1577
1578# Look at the leading whitespace in s.
1579# Return pair (# of leading ws characters,
1580# effective # of leading blanks after expanding
1581# tabs to width tabwidth)
1582
1583def classifyws(s, tabwidth):
1584 raw = effective = 0
1585 for ch in s:
1586 if ch == ' ':
1587 raw = raw + 1
1588 effective = effective + 1
1589 elif ch == '\t':
1590 raw = raw + 1
1591 effective = (effective // tabwidth + 1) * tabwidth
1592 else:
1593 break
1594 return raw, effective
1595
1596import tokenize
1597_tokenize = tokenize
1598del tokenize
1599
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001600class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001601
1602 # .run() chews over the Text widget, looking for a block opener
1603 # and the stmt following it. Returns a pair,
1604 # (line containing block opener, line containing stmt)
1605 # Either or both may be None.
1606
1607 def __init__(self, text, tabwidth):
1608 self.text = text
1609 self.tabwidth = tabwidth
1610 self.i = self.finished = 0
1611 self.blkopenline = self.indentedline = None
1612
1613 def readline(self):
1614 if self.finished:
1615 return ""
1616 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001617 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001618 if self.text.compare(mark, ">=", "end"):
1619 return ""
1620 return self.text.get(mark, mark + " lineend+1c")
1621
1622 def tokeneater(self, type, token, start, end, line,
1623 INDENT=_tokenize.INDENT,
1624 NAME=_tokenize.NAME,
1625 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1626 if self.finished:
1627 pass
1628 elif type == NAME and token in OPENERS:
1629 self.blkopenline = line
1630 elif type == INDENT and self.blkopenline:
1631 self.indentedline = line
1632 self.finished = 1
1633
1634 def run(self):
1635 save_tabsize = _tokenize.tabsize
1636 _tokenize.tabsize = self.tabwidth
1637 try:
1638 try:
Trent Nelson428de652008-03-18 22:41:35 +00001639 tokens = _tokenize.generate_tokens(self.readline)
1640 for token in tokens:
1641 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001642 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001643 # since we cut off the tokenizer early, we can trigger
1644 # spurious errors
1645 pass
1646 finally:
1647 _tokenize.tabsize = save_tabsize
1648 return self.blkopenline, self.indentedline
1649
1650### end autoindent code ###
1651
David Scherer7aced172000-08-15 01:13:23 +00001652def prepstr(s):
1653 # Helper to extract the underscore from a string, e.g.
1654 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001655 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001656 if i >= 0:
1657 s = s[:i] + s[i+1:]
1658 return i, s
1659
1660
1661keynames = {
1662 'bracketleft': '[',
1663 'bracketright': ']',
1664 'slash': '/',
1665}
1666
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001667def get_accelerator(keydefs, eventname):
1668 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001669 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1670 # if not keylist:
1671 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1672 "<<open-module>>",
1673 "<<goto-line>>",
1674 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001675 return ""
1676 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001677 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001678 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1679 s = re.sub("Key-", "", s)
1680 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1681 s = re.sub("Control-", "Ctrl-", s)
1682 s = re.sub("-", "+", s)
1683 s = re.sub("><", " ", s)
1684 s = re.sub("<", "", s)
1685 s = re.sub(">", "", s)
1686 return s
1687
1688
1689def fixwordbreaks(root):
1690 # Make sure that Tk's double-click and next/previous word
1691 # operations use our definition of a word (i.e. an identifier)
1692 tk = root.tk
1693 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1694 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1695 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1696
1697
1698def test():
1699 root = Tk()
1700 fixwordbreaks(root)
1701 root.withdraw()
1702 if sys.argv[1:]:
1703 filename = sys.argv[1]
1704 else:
1705 filename = None
1706 edit = EditorWindow(root=root, filename=filename)
1707 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001708 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001709 root.mainloop()
1710 root.destroy()
1711
1712if __name__ == '__main__':
1713 test()