blob: 0a01c9ec3051d3e26ab263b3cc41d70acbde57f4 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
David Scherer7aced172000-08-15 01:13:23 +00003import re
4import imp
Georg Brandl6634bf22008-05-20 07:13:37 +00005from Tkinter import *
6import tkSimpleDialog
7import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00008import webbrowser
Florent Xiclunad630c042010-04-02 07:24:52 +00009
10from idlelib.MultiCall import MultiCallCreator
11from idlelib import idlever
12from idlelib import WindowList
13from idlelib import SearchDialog
14from idlelib import GrepDialog
15from idlelib import ReplaceDialog
16from idlelib import PyParse
17from idlelib.configHandler import idleConf
18from idlelib import aboutDialog, textView, configDialog
19from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000020
21# The default tab setting for a Text widget, in average-width characters.
22TK_TABWIDTH_DEFAULT = 8
23
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000024def _sphinx_version():
25 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
26 major, minor, micro, level, serial = sys.version_info
27 release = '%s%s' % (major, minor)
28 if micro:
Benjamin Petersonfb234632009-06-13 15:42:23 +000029 release += '%s' % (micro,)
30 if level == 'candidate':
31 release += 'rc%s' % (serial,)
32 elif level != 'final':
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000033 release += '%s%s' % (level[0], serial)
34 return release
35
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000036def _find_module(fullname, path=None):
37 """Version of imp.find_module() that handles hierarchical module names"""
38
39 file = None
40 for tgt in fullname.split('.'):
41 if file is not None:
42 file.close() # close intermediate files
43 (file, filename, descr) = imp.find_module(tgt, path)
44 if descr[2] == imp.PY_SOURCE:
45 break # find but not load the source file
46 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000047 try:
48 path = module.__path__
49 except AttributeError:
50 raise ImportError, 'No source for module ' + module.__name__
Raymond Hettinger179816d2011-04-12 18:54:46 -070051 if descr[2] != imp.PY_SOURCE:
52 # If all of the above fails and didn't raise an exception,fallback
53 # to a straight import which can find __init__.py in a package.
54 m = __import__(fullname)
55 try:
56 filename = m.__file__
57 except AttributeError:
58 pass
59 else:
60 file = None
61 base, ext = os.path.splitext(filename)
62 if ext == '.pyc':
63 ext = '.py'
64 filename = base + ext
65 descr = filename, None, imp.PY_SOURCE
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000066 return file, filename, descr
67
Terry Jan Reedyadb87e22012-02-05 15:10:55 -050068
69class HelpDialog(object):
70
71 def __init__(self):
72 self.parent = None # parent of help window
73 self.dlg = None # the help window iteself
74
75 def display(self, parent, near=None):
76 """ Display the help dialog.
77
78 parent - parent widget for the help window
79
80 near - a Toplevel widget (e.g. EditorWindow or PyShell)
81 to use as a reference for placing the help window
82 """
83 if self.dlg is None:
84 self.show_dialog(parent)
85 if near:
86 self.nearwindow(near)
87
88 def show_dialog(self, parent):
89 self.parent = parent
90 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
91 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
92 dlg.bind('<Destroy>', self.destroy, '+')
93
94 def nearwindow(self, near):
95 # Place the help dialog near the window specified by parent.
96 # Note - this may not reposition the window in Metacity
97 # if "/apps/metacity/general/disable_workarounds" is enabled
98 dlg = self.dlg
99 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
100 dlg.withdraw()
101 dlg.geometry("=+%d+%d" % geom)
102 dlg.deiconify()
103 dlg.lift()
104
105 def destroy(self, ev=None):
106 self.dlg = None
107 self.parent = None
108
109helpDialog = HelpDialog() # singleton instance
110
111
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000112class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +0000113 from idlelib.Percolator import Percolator
114 from idlelib.ColorDelegator import ColorDelegator
115 from idlelib.UndoDelegator import UndoDelegator
116 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
117 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +0000118 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +0000119 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000120
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000121 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000122
123 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000124 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +0000125 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000126 if sys.platform.count('linux'):
127 # look for html docs in a couple of standard places
128 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
129 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
130 dochome = '/var/www/html/python/index.html'
131 else:
132 basepath = '/usr/share/doc/' # standard location
133 dochome = os.path.join(basepath, pyver,
134 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000135 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +0000136 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +0000137 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000138 if os.path.isfile(chmfile):
139 dochome = chmfile
Ronald Oussoren19302d92006-06-11 14:33:36 +0000140 elif macosxSupport.runningAsOSXApp():
141 # documentation is stored inside the python framework
142 dochome = os.path.join(sys.prefix,
143 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000144 dochome = os.path.normpath(dochome)
145 if os.path.isfile(dochome):
146 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +0000147 if sys.platform == 'darwin':
148 # Safari requires real file:-URLs
149 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000150 else:
Raymond Hettinger7b4c2be2009-01-20 10:46:23 +0000151 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000152 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000153 self.flist = flist
154 root = root or flist.root
155 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +0000156 try:
157 sys.ps1
158 except AttributeError:
159 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000160 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000161 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000162 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000163 self.tkinter_vars = flist.vars
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200164 #self.top.instance_dict makes flist.inversedict available to
165 #configDialog.py so it can access all EditorWindow instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000166 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000167 else:
168 self.tkinter_vars = {} # keys: Tkinter event names
169 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000170 self.top.instance_dict = {}
171 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000172 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000173 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000174 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200175 self.width = idleConf.GetOption('main','EditorWindow','width', type='int')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000176 text_options = {
177 'name': 'text',
178 'padx': 5,
179 'wrap': 'none',
180 'width': self.width,
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200181 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')}
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000182 if TkVersion >= 8.5:
183 # Starting with tk 8.5 we have to set the new tabstyle option
184 # to 'wordprocessor' to achieve the same display of tabs as in
185 # older tk versions.
186 text_options['tabstyle'] = 'wordprocessor'
187 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000188 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000189
190 self.createmenubar()
191 self.apply_bindings()
192
193 self.top.protocol("WM_DELETE_WINDOW", self.close)
194 self.top.bind("<<close-window>>", self.close_event)
Ronald Oussoren17db4952006-07-23 09:41:09 +0000195 if macosxSupport.runningAsOSXApp():
196 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000197 text.bind('<<close-window>>', self.close_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000198 # Some OS X systems have only one mouse button,
199 # so use control-click for pulldown menus there.
200 # (Note, AquaTk defines <2> as the right button if
201 # present and the Tk Text widget already binds <2>.)
202 text.bind("<Control-Button-1>",self.right_menu_event)
203 else:
204 # Elsewhere, use right-click for pulldown menus.
205 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000206 text.bind("<<cut>>", self.cut)
207 text.bind("<<copy>>", self.copy)
208 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000209 text.bind("<<center-insert>>", self.center_insert_event)
210 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000211 text.bind("<<python-docs>>", self.python_docs)
212 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000213 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000214 text.bind("<<open-module>>", self.open_module)
215 text.bind("<<do-nothing>>", lambda event: "break")
216 text.bind("<<select-all>>", self.select_all)
217 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000218 text.bind("<<find>>", self.find_event)
219 text.bind("<<find-again>>", self.find_again_event)
220 text.bind("<<find-in-files>>", self.find_in_files_event)
221 text.bind("<<find-selection>>", self.find_selection_event)
222 text.bind("<<replace>>", self.replace_event)
223 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000224 text.bind("<<smart-backspace>>",self.smart_backspace_event)
225 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
226 text.bind("<<smart-indent>>",self.smart_indent_event)
227 text.bind("<<indent-region>>",self.indent_region_event)
228 text.bind("<<dedent-region>>",self.dedent_region_event)
229 text.bind("<<comment-region>>",self.comment_region_event)
230 text.bind("<<uncomment-region>>",self.uncomment_region_event)
231 text.bind("<<tabify-region>>",self.tabify_region_event)
232 text.bind("<<untabify-region>>",self.untabify_region_event)
233 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
234 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000235 text.bind("<Left>", self.move_at_edge_if_selection(0))
236 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000237 text.bind("<<del-word-left>>", self.del_word_left)
238 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000239 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000240
David Scherer7aced172000-08-15 01:13:23 +0000241 if flist:
242 flist.inversedict[self] = key
243 if key:
244 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000245 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000246 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
247 text.bind("<<open-class-browser>>", self.open_class_browser)
248 text.bind("<<open-path-browser>>", self.open_path_browser)
249
Steven M. Gava898a3652001-10-07 11:10:44 +0000250 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000251 vbar['command'] = text.yview
252 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000253 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000254 fontWeight = 'normal'
255 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000256 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000257 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200258 idleConf.GetOption('main', 'EditorWindow',
259 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000260 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000261 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
262 text.pack(side=TOP, fill=BOTH, expand=1)
263 text.focus_set()
264
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000265 # usetabs true -> literal tab characters are used by indent and
266 # dedent cmds, possibly mixed with spaces if
267 # indentwidth is not a multiple of tabwidth,
268 # which will cause Tabnanny to nag!
269 # false -> tab characters are converted to spaces by indent
270 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000271 # Although use-spaces=0 can be configured manually in config-main.def,
272 # configuration of tabs v. spaces is not supported in the configuration
273 # dialog. IDLE promotes the preferred Python indentation: use spaces!
274 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
275 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000276
277 # tabwidth is the display width of a literal tab character.
278 # CAUTION: telling Tk to use anything other than its default
279 # tab setting causes it to use an entirely different tabbing algorithm,
280 # treating tab stops as fixed distances from the left margin.
281 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000282 self.tabwidth = 8 # must remain 8 until Tk is fixed.
283
284 # indentwidth is the number of screen characters per indent level.
285 # The recommended Python indentation is four spaces.
286 self.indentwidth = self.tabwidth
287 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000288
289 # If context_use_ps1 is true, parsing searches back for a ps1 line;
290 # else searches for a popular (if, def, ...) Python stmt.
291 self.context_use_ps1 = False
292
293 # When searching backwards for a reliable place to begin parsing,
294 # first start num_context_lines[0] lines back, then
295 # num_context_lines[1] lines back if that didn't work, and so on.
296 # The last value should be huge (larger than the # of lines in a
297 # conceivable file).
298 # Making the initial values larger slows things down more often.
299 self.num_context_lines = 50, 500, 5000000
300
David Scherer7aced172000-08-15 01:13:23 +0000301 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000302
303 self.undo = undo = self.UndoDelegator()
304 per.insertfilter(undo)
305 text.undo_block_start = undo.undo_block_start
306 text.undo_block_stop = undo.undo_block_stop
307 undo.set_saved_change_hook(self.saved_change_hook)
308
309 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000310 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000311 io.set_filename_change_hook(self.filename_change_hook)
312
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000313 # Create the recent files submenu
314 self.recent_files_menu = Menu(self.menubar)
315 self.menudict['file'].insert_cascade(3, label='Recent Files',
316 underline=0,
317 menu=self.recent_files_menu)
318 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000319
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000320 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000321 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000322 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000323 io.loadfile(filename)
324 else:
325 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000326 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000327 self.saved_change_hook()
328
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000329 self.set_indentation_params(self.ispythonsource(filename))
330
David Scherer7aced172000-08-15 01:13:23 +0000331 self.load_extensions()
332
333 menu = self.menudict.get('windows')
334 if menu:
335 end = menu.index("end")
336 if end is None:
337 end = -1
338 if end >= 0:
339 menu.add_separator()
340 end = end + 1
341 self.wmenu_end = end
342 WindowList.register_callback(self.postwindowsmenu)
343
344 # Some abstractions so IDLE extensions are cross-IDE
345 self.askyesno = tkMessageBox.askyesno
346 self.askinteger = tkSimpleDialog.askinteger
347 self.showerror = tkMessageBox.showerror
348
Martin v. Löwis307021f2005-11-27 16:59:04 +0000349 def _filename_to_unicode(self, filename):
350 """convert filename to unicode in order to display it in Tk"""
351 if isinstance(filename, unicode) or not filename:
352 return filename
353 else:
354 try:
355 return filename.decode(self.filesystemencoding)
356 except UnicodeDecodeError:
357 # XXX
358 try:
359 return filename.decode(self.encoding)
360 except UnicodeDecodeError:
361 # byte-to-byte conversion
362 return filename.decode('iso8859-1')
363
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000364 def new_callback(self, event):
365 dirname, basename = self.io.defaultfilename()
366 self.flist.new(dirname)
367 return "break"
368
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000369 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400370 if (event.state & 4) != 0 and event.keysym == "Home":
371 # state&4==Control. If <Control-Home>, use the Tk binding.
372 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000373 if self.text.index("iomark") and \
374 self.text.compare("iomark", "<=", "insert lineend") and \
375 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400376 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000377 insertpt = int(self.text.index("iomark").split(".")[1])
378 else:
379 line = self.text.get("insert linestart", "insert lineend")
380 for insertpt in xrange(len(line)):
381 if line[insertpt] not in (' ','\t'):
382 break
383 else:
384 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000385 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000386 if insertpt == lineat:
387 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000388 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000389 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400390 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000391 self.text.tag_remove("sel", "1.0", "end")
392 else:
393 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400394 self.text.mark_set("my_anchor", "insert") # there was no previous selection
395 else:
396 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
397 self.text.mark_set("my_anchor", "sel.first") # extend back
398 else:
399 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000400 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400401 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000402 if self.text.compare(first,">",last):
403 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000404 self.text.tag_remove("sel", "1.0", "end")
405 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000406 self.text.mark_set("insert", dest)
407 self.text.see("insert")
408 return "break"
409
David Scherer7aced172000-08-15 01:13:23 +0000410 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000411 self.status_bar = self.MultiStatusBar(self.top)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000412 if macosxSupport.runningAsOSXApp():
413 # Insert some padding to avoid obscuring some of the statusbar
414 # by the resize widget.
415 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000416 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
417 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
418 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000419 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
420 self.text.event_add("<<set-line-and-column>>",
421 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000422 self.text.after_idle(self.set_line_and_column)
423
424 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000425 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000426 self.status_bar.set_label('column', 'Col: %s' % column)
427 self.status_bar.set_label('line', 'Ln: %s' % line)
428
David Scherer7aced172000-08-15 01:13:23 +0000429 menu_specs = [
430 ("file", "_File"),
431 ("edit", "_Edit"),
432 ("format", "F_ormat"),
433 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000434 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000435 ("windows", "_Windows"),
436 ("help", "_Help"),
437 ]
438
Ronald Oussoren19302d92006-06-11 14:33:36 +0000439 if macosxSupport.runningAsOSXApp():
440 del menu_specs[-3]
441 menu_specs[-2] = ("windows", "_Window")
442
443
David Scherer7aced172000-08-15 01:13:23 +0000444 def createmenubar(self):
445 mbar = self.menubar
446 self.menudict = menudict = {}
447 for name, label in self.menu_specs:
448 underline, label = prepstr(label)
449 menudict[name] = menu = Menu(mbar, name=name)
450 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000451
Ned Deily4a705502011-01-18 04:33:22 +0000452 if macosxSupport.isCarbonAquaTk(self.root):
Ronald Oussoren19302d92006-06-11 14:33:36 +0000453 # Insert the application menu
454 menudict['application'] = menu = Menu(mbar, name='apple')
455 mbar.add_cascade(label='IDLE', menu=menu)
456
David Scherer7aced172000-08-15 01:13:23 +0000457 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000458 self.base_helpmenu_length = self.menudict['help'].index(END)
459 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000460
461 def postwindowsmenu(self):
462 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000463 menu = self.menudict['windows']
464 end = menu.index("end")
465 if end is None:
466 end = -1
467 if end > self.wmenu_end:
468 menu.delete(self.wmenu_end+1, end)
469 WindowList.add_windows_to_menu(menu)
470
471 rmenu = None
472
473 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000474 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
475 if not self.rmenu:
476 self.make_rmenu()
477 rmenu = self.rmenu
478 self.event = event
479 iswin = sys.platform[:3] == 'win'
480 if iswin:
481 self.text.config(cursor="arrow")
Andrew Svetlov5018db72012-11-01 22:39:14 +0200482
483 for label, eventname, verify_state in self.rmenu_specs:
484 if verify_state is None:
485 continue
486 state = getattr(self, verify_state)()
487 rmenu.entryconfigure(label, state=state)
488
David Scherer7aced172000-08-15 01:13:23 +0000489 rmenu.tk_popup(event.x_root, event.y_root)
490 if iswin:
491 self.text.config(cursor="ibeam")
492
493 rmenu_specs = [
Andrew Svetlov5018db72012-11-01 22:39:14 +0200494 # ("Label", "<<virtual-event>>", "statefuncname"), ...
495 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000496 ]
497
498 def make_rmenu(self):
499 rmenu = Menu(self.text, tearoff=0)
Andrew Svetlov5018db72012-11-01 22:39:14 +0200500 for label, eventname, _ in self.rmenu_specs:
501 if label is not None:
502 def command(text=self.text, eventname=eventname):
503 text.event_generate(eventname)
504 rmenu.add_command(label=label, command=command)
505 else:
506 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000507 self.rmenu = rmenu
508
Andrew Svetlov5018db72012-11-01 22:39:14 +0200509 def rmenu_check_cut(self):
510 return self.rmenu_check_copy()
511
512 def rmenu_check_copy(self):
513 try:
514 indx = self.text.index('sel.first')
515 except TclError:
516 return 'disabled'
517 else:
518 return 'normal' if indx else 'disabled'
519
520 def rmenu_check_paste(self):
521 try:
522 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
523 except TclError:
524 return 'disabled'
525 else:
526 return 'normal'
527
David Scherer7aced172000-08-15 01:13:23 +0000528 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000529 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000530
Steven M. Gava3b55a892001-11-21 05:56:26 +0000531 def config_dialog(self, event=None):
532 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000533
David Scherer7aced172000-08-15 01:13:23 +0000534 def help_dialog(self, event=None):
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500535 if self.root:
536 parent = self.root
537 else:
538 parent = self.top
539 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000540
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000541 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000542 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000543 try:
544 os.startfile(self.help_url)
545 except WindowsError as why:
546 tkMessageBox.showerror(title='Document Start Failure',
547 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000548 else:
549 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000550 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000551
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000552 def cut(self,event):
553 self.text.event_generate("<<Cut>>")
554 return "break"
555
556 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000557 if not self.text.tag_ranges("sel"):
558 # There is no selection, so do nothing and maybe interrupt.
559 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000560 self.text.event_generate("<<Copy>>")
561 return "break"
562
563 def paste(self,event):
564 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000565 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000566 return "break"
567
David Scherer7aced172000-08-15 01:13:23 +0000568 def select_all(self, event=None):
569 self.text.tag_add("sel", "1.0", "end-1c")
570 self.text.mark_set("insert", "1.0")
571 self.text.see("insert")
572 return "break"
573
574 def remove_selection(self, event=None):
575 self.text.tag_remove("sel", "1.0", "end")
576 self.text.see("insert")
577
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000578 def move_at_edge_if_selection(self, edge_index):
579 """Cursor move begins at start or end of selection
580
581 When a left/right cursor key is pressed create and return to Tkinter a
582 function which causes a cursor move from the associated edge of the
583 selection.
584
585 """
586 self_text_index = self.text.index
587 self_text_mark_set = self.text.mark_set
588 edges_table = ("sel.first+1c", "sel.last-1c")
589 def move_at_edge(event):
590 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
591 try:
592 self_text_index("sel.first")
593 self_text_mark_set("insert", edges_table[edge_index])
594 except TclError:
595 pass
596 return move_at_edge
597
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000598 def del_word_left(self, event):
599 self.text.event_generate('<Meta-Delete>')
600 return "break"
601
602 def del_word_right(self, event):
603 self.text.event_generate('<Meta-d>')
604 return "break"
605
Steven M. Gavac5976402002-01-04 03:06:08 +0000606 def find_event(self, event):
607 SearchDialog.find(self.text)
608 return "break"
609
610 def find_again_event(self, event):
611 SearchDialog.find_again(self.text)
612 return "break"
613
614 def find_selection_event(self, event):
615 SearchDialog.find_selection(self.text)
616 return "break"
617
618 def find_in_files_event(self, event):
619 GrepDialog.grep(self.text, self.io, self.flist)
620 return "break"
621
622 def replace_event(self, event):
623 ReplaceDialog.replace(self.text)
624 return "break"
625
626 def goto_line_event(self, event):
627 text = self.text
628 lineno = tkSimpleDialog.askinteger("Goto",
629 "Go to line number:",parent=text)
630 if lineno is None:
631 return "break"
632 if lineno <= 0:
633 text.bell()
634 return "break"
635 text.mark_set("insert", "%d.0" % lineno)
636 text.see("insert")
637
David Scherer7aced172000-08-15 01:13:23 +0000638 def open_module(self, event=None):
639 # XXX Shouldn't this be in IOBinding or in FileList?
640 try:
641 name = self.text.get("sel.first", "sel.last")
642 except TclError:
643 name = ""
644 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000645 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000646 name = tkSimpleDialog.askstring("Module",
647 "Enter the name of a Python module\n"
648 "to search on sys.path and open:",
649 parent=self.text, initialvalue=name)
650 if name:
651 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000652 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000653 return
David Scherer7aced172000-08-15 01:13:23 +0000654 # XXX Ought to insert current file's directory in front of path
655 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000656 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000657 except (NameError, ImportError), msg:
658 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
659 return
660 if type != imp.PY_SOURCE:
661 tkMessageBox.showerror("Unsupported type",
662 "%s is not a source module" % name, parent=self.text)
663 return
664 if f:
665 f.close()
666 if self.flist:
667 self.flist.open(file)
668 else:
669 self.io.loadfile(file)
670
671 def open_class_browser(self, event=None):
672 filename = self.io.filename
673 if not filename:
674 tkMessageBox.showerror(
675 "No filename",
676 "This buffer has no associated filename",
677 master=self.text)
678 self.text.focus_set()
679 return None
680 head, tail = os.path.split(filename)
681 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000682 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000683 ClassBrowser.ClassBrowser(self.flist, base, [head])
684
685 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000686 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000687 PathBrowser.PathBrowser(self.flist)
688
689 def gotoline(self, lineno):
690 if lineno is not None and lineno > 0:
691 self.text.mark_set("insert", "%d.0" % lineno)
692 self.text.tag_remove("sel", "1.0", "end")
693 self.text.tag_add("sel", "insert", "insert +1l")
694 self.center()
695
696 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000697 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000698 return True
David Scherer7aced172000-08-15 01:13:23 +0000699 base, ext = os.path.splitext(os.path.basename(filename))
700 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000701 return True
David Scherer7aced172000-08-15 01:13:23 +0000702 try:
703 f = open(filename)
704 line = f.readline()
705 f.close()
706 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000707 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000708 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000709
710 def close_hook(self):
711 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000712 self.flist.unregister_maybe_terminate(self)
713 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000714
715 def set_close_hook(self, close_hook):
716 self.close_hook = close_hook
717
718 def filename_change_hook(self):
719 if self.flist:
720 self.flist.filename_changed_edit(self)
721 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000722 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000723 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000724
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000725 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000726 if self.color:
727 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000728 if self.ispythonsource(self.io.filename):
729 self.color = self.ColorDelegator()
730 # can add more colorizers here...
731 if self.color:
732 self.per.removefilter(self.undo)
733 self.per.insertfilter(self.color)
734 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000735
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000736 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000737 if not self.color:
738 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000739 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000740 self.per.removefilter(self.color)
741 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000742
Steven M. Gavab77d3432002-03-02 07:16:21 +0000743 def ResetColorizer(self):
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000744 "Update the colour theme"
745 # Called from self.filename_change_hook and from configDialog.py
746 self._rmcolorizer()
747 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000748 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000749 normal_colors = idleConf.GetHighlight(theme, 'normal')
750 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
751 select_colors = idleConf.GetHighlight(theme, 'hilite')
752 self.text.config(
753 foreground=normal_colors['foreground'],
754 background=normal_colors['background'],
755 insertbackground=cursor_color,
756 selectforeground=select_colors['foreground'],
757 selectbackground=select_colors['background'],
758 )
David Scherer7aced172000-08-15 01:13:23 +0000759
Steven M. Gavab1585412002-03-12 00:21:56 +0000760 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000761 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000762 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000763 fontWeight='normal'
764 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
765 fontWeight='bold'
766 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200767 idleConf.GetOption('main','EditorWindow','font-size',
768 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000769 fontWeight))
770
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000771 def RemoveKeybindings(self):
772 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000773 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000774 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000775 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000776 self.text.event_delete(event, *keylist)
777 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000778 xkeydefs = idleConf.GetExtensionBindings(extensionName)
779 if xkeydefs:
780 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000781 self.text.event_delete(event, *keylist)
782
783 def ApplyKeybindings(self):
784 "Update the keybindings after they are changed"
785 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000786 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000787 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000788 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000789 xkeydefs = idleConf.GetExtensionBindings(extensionName)
790 if xkeydefs:
791 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000792 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000793 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000794 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000795 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000796 for item in menu[1]:
797 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000798 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000799 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000800 menu = self.menudict[menubarItem]
801 end = menu.index(END) + 1
802 for index in range(0, end):
803 if menu.type(index) == 'command':
804 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000805 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000806 itemName = menu.entrycget(index, 'label')
807 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000808 if menubarItem in menuEventDict:
809 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000810 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000811 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000812 accel = get_accelerator(keydefs, event)
813 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000814
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000815 def set_notabs_indentwidth(self):
816 "Update the indentwidth if changed and not using tabs in this window"
817 # Called from configDialog.py
818 if not self.usetabs:
819 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
820 type='int')
821
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000822 def reset_help_menu_entries(self):
823 "Update the additional help entries on the Help menu"
824 help_list = idleConf.GetAllExtraHelpSourcesList()
825 helpmenu = self.menudict['help']
826 # first delete the extra help entries, if any
827 helpmenu_length = helpmenu.index(END)
828 if helpmenu_length > self.base_helpmenu_length:
829 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
830 # then rebuild them
831 if help_list:
832 helpmenu.add_separator()
833 for entry in help_list:
834 cmd = self.__extra_help_callback(entry[1])
835 helpmenu.add_command(label=entry[0], command=cmd)
836 # and update the menu dictionary
837 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000838
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000839 def __extra_help_callback(self, helpfile):
840 "Create a callback with the helpfile value frozen at definition time"
841 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000842 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000843 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000844 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000845 try:
846 os.startfile(helpfile)
847 except WindowsError as why:
848 tkMessageBox.showerror(title='Document Start Failure',
849 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000850 else:
851 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000852 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000853
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000854 def update_recent_files_list(self, new_file=None):
855 "Load and update the recent files list and menus"
856 rf_list = []
857 if os.path.exists(self.recent_files_path):
858 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000859 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000860 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000861 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000862 rf_list_file.close()
863 if new_file:
864 new_file = os.path.abspath(new_file) + '\n'
865 if new_file in rf_list:
866 rf_list.remove(new_file) # move to top
867 rf_list.insert(0, new_file)
868 # clean and save the recent files list
869 bad_paths = []
870 for path in rf_list:
871 if '\0' in path or not os.path.exists(path[0:-1]):
872 bad_paths.append(path)
873 rf_list = [path for path in rf_list if path not in bad_paths]
874 ulchars = "1234567890ABCDEFGHIJK"
875 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000876 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800877 with open(self.recent_files_path, 'w') as rf_file:
878 rf_file.writelines(rf_list)
879 except IOError as err:
880 if not getattr(self.root, "recentfilelist_error_displayed", False):
881 self.root.recentfilelist_error_displayed = True
882 tkMessageBox.showerror(title='IDLE Error',
883 message='Unable to update Recent Files list:\n%s'
884 % str(err),
885 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000886 # for each edit window instance, construct the recent files menu
887 for instance in self.top.instance_dict.keys():
888 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700889 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000890 for i, file_name in enumerate(rf_list):
891 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000892 # make unicode string to display non-ASCII chars correctly
893 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000894 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000895 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000896 command=callback,
897 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000898
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000899 def __recent_file_callback(self, file_name):
900 def open_recent_file(fn_closure=file_name):
901 self.io.open(editFile=fn_closure)
902 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000903
David Scherer7aced172000-08-15 01:13:23 +0000904 def saved_change_hook(self):
905 short = self.short_title()
906 long = self.long_title()
907 if short and long:
908 title = short + " - " + long
909 elif short:
910 title = short
911 elif long:
912 title = long
913 else:
914 title = "Untitled"
915 icon = short or long or title
916 if not self.get_saved():
917 title = "*%s*" % title
918 icon = "*%s" % icon
919 self.top.wm_title(title)
920 self.top.wm_iconname(icon)
921
922 def get_saved(self):
923 return self.undo.get_saved()
924
925 def set_saved(self, flag):
926 self.undo.set_saved(flag)
927
928 def reset_undo(self):
929 self.undo.reset_undo()
930
931 def short_title(self):
932 filename = self.io.filename
933 if filename:
934 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000935 # return unicode string to display non-ASCII chars correctly
936 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000937
938 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000939 # return unicode string to display non-ASCII chars correctly
940 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000941
942 def center_insert_event(self, event):
943 self.center()
944
945 def center(self, mark="insert"):
946 text = self.text
947 top, bot = self.getwindowlines()
948 lineno = self.getlineno(mark)
949 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000950 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000951 text.yview(float(newtop))
952
953 def getwindowlines(self):
954 text = self.text
955 top = self.getlineno("@0,0")
956 bot = self.getlineno("@0,65535")
957 if top == bot and text.winfo_height() == 1:
958 # Geometry manager hasn't run yet
959 height = int(text['height'])
960 bot = top + height - 1
961 return top, bot
962
963 def getlineno(self, mark="insert"):
964 text = self.text
965 return int(float(text.index(mark)))
966
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000967 def get_geometry(self):
968 "Return (width, height, x, y)"
969 geom = self.top.wm_geometry()
970 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
971 tuple = (map(int, m.groups()))
972 return tuple
973
David Scherer7aced172000-08-15 01:13:23 +0000974 def close_event(self, event):
975 self.close()
976
977 def maybesave(self):
978 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000979 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000980 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000981 self.top.deiconify()
982 self.top.lower()
983 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000984 return self.io.maybesave()
985
986 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000987 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +0000988 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000989 self._close()
990 return reply
991
992 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000993 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000994 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000995 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000996 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000997 self.io.close()
998 self.io = None
999 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001000 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001001 self.color.close(False)
1002 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001003 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001004 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001005 self.per.close()
1006 self.per = None
1007 self.top.destroy()
1008 if self.close_hook:
1009 # unless override: unregister from flist, terminate if last window
1010 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001011
1012 def load_extensions(self):
1013 self.extensions = {}
1014 self.load_standard_extensions()
1015
1016 def unload_extensions(self):
1017 for ins in self.extensions.values():
1018 if hasattr(ins, "close"):
1019 ins.close()
1020 self.extensions = {}
1021
1022 def load_standard_extensions(self):
1023 for name in self.get_standard_extension_names():
1024 try:
1025 self.load_extension(name)
1026 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001027 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001028 import traceback
1029 traceback.print_exc()
1030
1031 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001032 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001033
1034 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001035 try:
1036 mod = __import__(name, globals(), locals(), [])
1037 except ImportError:
1038 print "\nFailed to import extension: ", name
1039 return
David Scherer7aced172000-08-15 01:13:23 +00001040 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001041 keydefs = idleConf.GetExtensionBindings(name)
1042 if hasattr(cls, "menudefs"):
1043 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001044 ins = cls(self)
1045 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001046 if keydefs:
1047 self.apply_bindings(keydefs)
1048 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001049 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001050 while methodname[:1] == '<':
1051 methodname = methodname[1:]
1052 while methodname[-1:] == '>':
1053 methodname = methodname[:-1]
1054 methodname = methodname + "_event"
1055 if hasattr(ins, methodname):
1056 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001057
1058 def apply_bindings(self, keydefs=None):
1059 if keydefs is None:
1060 keydefs = self.Bindings.default_keydefs
1061 text = self.text
1062 text.keydefs = keydefs
1063 for event, keylist in keydefs.items():
1064 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001065 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001066
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001067 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001068 """Add appropriate entries to the menus and submenus
1069
1070 Menus that are absent or None in self.menudict are ignored.
1071 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001072 if menudefs is None:
1073 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001074 if keydefs is None:
1075 keydefs = self.Bindings.default_keydefs
1076 menudict = self.menudict
1077 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001078 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001079 menu = menudict.get(mname)
1080 if not menu:
1081 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001082 for entry in entrylist:
1083 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001084 menu.add_separator()
1085 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001086 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001087 checkbutton = (label[:1] == '!')
1088 if checkbutton:
1089 label = label[1:]
1090 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001091 accelerator = get_accelerator(keydefs, eventname)
1092 def command(text=text, eventname=eventname):
1093 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001094 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001095 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001096 menu.add_checkbutton(label=label, underline=underline,
1097 command=command, accelerator=accelerator,
1098 variable=var)
1099 else:
1100 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001101 command=command,
1102 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001103
1104 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001105 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001106 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001107 value = var.get()
1108 return value
1109 else:
1110 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001111
1112 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001113 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001114 if var:
1115 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001116 else:
1117 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001118
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001119 def get_var_obj(self, name, vartype=None):
1120 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001121 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001122 # create a Tkinter variable object with self.text as master:
1123 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001124 return var
1125
1126 # Tk implementations of "virtual text methods" -- each platform
1127 # reusing IDLE's support code needs to define these for its GUI's
1128 # flavor of widget.
1129
1130 # Is character at text_index in a Python string? Return 0 for
1131 # "guaranteed no", true for anything else. This info is expensive
1132 # to compute ab initio, but is probably already known by the
1133 # platform's colorizer.
1134
1135 def is_char_in_string(self, text_index):
1136 if self.color:
1137 # Return true iff colorizer hasn't (re)gotten this far
1138 # yet, or the character is tagged as being in a string
1139 return self.text.tag_prevrange("TODO", text_index) or \
1140 "STRING" in self.text.tag_names(text_index)
1141 else:
1142 # The colorizer is missing: assume the worst
1143 return 1
1144
1145 # If a selection is defined in the text widget, return (start,
1146 # end) as Tkinter text indices, otherwise return (None, None)
1147 def get_selection_indices(self):
1148 try:
1149 first = self.text.index("sel.first")
1150 last = self.text.index("sel.last")
1151 return first, last
1152 except TclError:
1153 return None, None
1154
1155 # Return the text widget's current view of what a tab stop means
1156 # (equivalent width in spaces).
1157
1158 def get_tabwidth(self):
1159 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1160 return int(current)
1161
1162 # Set the text widget's current view of what a tab stop means.
1163
1164 def set_tabwidth(self, newtabwidth):
1165 text = self.text
1166 if self.get_tabwidth() != newtabwidth:
1167 pixels = text.tk.call("font", "measure", text["font"],
1168 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001169 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001170 text.configure(tabs=pixels)
1171
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001172 # If ispythonsource and guess are true, guess a good value for
1173 # indentwidth based on file content (if possible), and if
1174 # indentwidth != tabwidth set usetabs false.
1175 # In any case, adjust the Text widget's view of what a tab
1176 # character means.
1177
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001178 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001179 if guess and ispythonsource:
1180 i = self.guess_indent()
1181 if 2 <= i <= 8:
1182 self.indentwidth = i
1183 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001184 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001185 self.set_tabwidth(self.tabwidth)
1186
1187 def smart_backspace_event(self, event):
1188 text = self.text
1189 first, last = self.get_selection_indices()
1190 if first and last:
1191 text.delete(first, last)
1192 text.mark_set("insert", first)
1193 return "break"
1194 # Delete whitespace left, until hitting a real char or closest
1195 # preceding virtual tab stop.
1196 chars = text.get("insert linestart", "insert")
1197 if chars == '':
1198 if text.compare("insert", ">", "1.0"):
1199 # easy: delete preceding newline
1200 text.delete("insert-1c")
1201 else:
1202 text.bell() # at start of buffer
1203 return "break"
1204 if chars[-1] not in " \t":
1205 # easy: delete preceding real char
1206 text.delete("insert-1c")
1207 return "break"
1208 # Ick. It may require *inserting* spaces if we back up over a
1209 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001210 tabwidth = self.tabwidth
1211 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001212 assert have > 0
1213 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001214 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001215 if self.context_use_ps1:
1216 last_line_of_prompt = sys.ps1.split('\n')[-1]
1217 else:
1218 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001219 ncharsdeleted = 0
1220 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001221 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001222 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001223 chars = chars[:-1]
1224 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001225 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001226 if have <= want or chars[-1] not in " \t":
1227 break
1228 text.undo_block_start()
1229 text.delete("insert-%dc" % ncharsdeleted, "insert")
1230 if have < want:
1231 text.insert("insert", ' ' * (want - have))
1232 text.undo_block_stop()
1233 return "break"
1234
1235 def smart_indent_event(self, event):
1236 # if intraline selection:
1237 # delete it
1238 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001239 # do indent-region
1240 # else:
1241 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001242 text = self.text
1243 first, last = self.get_selection_indices()
1244 text.undo_block_start()
1245 try:
1246 if first and last:
1247 if index2line(first) != index2line(last):
1248 return self.indent_region_event(event)
1249 text.delete(first, last)
1250 text.mark_set("insert", first)
1251 prefix = text.get("insert linestart", "insert")
1252 raw, effective = classifyws(prefix, self.tabwidth)
1253 if raw == len(prefix):
1254 # only whitespace to the left
1255 self.reindent_to(effective + self.indentwidth)
1256 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001257 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001258 if self.usetabs:
1259 pad = '\t'
1260 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001261 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001262 n = self.indentwidth
1263 pad = ' ' * (n - effective % n)
1264 text.insert("insert", pad)
1265 text.see("insert")
1266 return "break"
1267 finally:
1268 text.undo_block_stop()
1269
1270 def newline_and_indent_event(self, event):
1271 text = self.text
1272 first, last = self.get_selection_indices()
1273 text.undo_block_start()
1274 try:
1275 if first and last:
1276 text.delete(first, last)
1277 text.mark_set("insert", first)
1278 line = text.get("insert linestart", "insert")
1279 i, n = 0, len(line)
1280 while i < n and line[i] in " \t":
1281 i = i+1
1282 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001283 # the cursor is in or at leading indentation in a continuation
1284 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001285 text.insert("insert linestart", '\n')
1286 return "break"
1287 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001288 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001289 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001290 last_line_of_prompt = sys.ps1.split('\n')[-1]
1291 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001292 line = line[:-1]
1293 i = i+1
1294 if i:
1295 text.delete("insert - %d chars" % i, "insert")
1296 # strip whitespace after insert point
1297 while text.get("insert") in " \t":
1298 text.delete("insert")
1299 # start new line
1300 text.insert("insert", '\n')
1301
1302 # adjust indentation for continuations and block
1303 # open/close first need to find the last stmt
1304 lno = index2line(text.index('insert'))
1305 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001306 if not self.context_use_ps1:
1307 for context in self.num_context_lines:
1308 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001309 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001310 rawtext = text.get(startatindex, "insert")
1311 y.set_str(rawtext)
1312 bod = y.find_good_parse_start(
1313 self.context_use_ps1,
1314 self._build_char_in_string_func(startatindex))
1315 if bod is not None or startat == 1:
1316 break
1317 y.set_lo(bod or 0)
1318 else:
1319 r = text.tag_prevrange("console", "insert")
1320 if r:
1321 startatindex = r[1]
1322 else:
1323 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001324 rawtext = text.get(startatindex, "insert")
1325 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001326 y.set_lo(0)
1327
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001328 c = y.get_continuation_type()
1329 if c != PyParse.C_NONE:
1330 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001331 if c == PyParse.C_STRING_FIRST_LINE:
1332 # after the first line of a string; do not indent at all
1333 pass
1334 elif c == PyParse.C_STRING_NEXT_LINES:
1335 # inside a string which started before this line;
1336 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001337 text.insert("insert", indent)
1338 elif c == PyParse.C_BRACKET:
1339 # line up with the first (if any) element of the
1340 # last open bracket structure; else indent one
1341 # level beyond the indent of the line with the
1342 # last open bracket
1343 self.reindent_to(y.compute_bracket_indent())
1344 elif c == PyParse.C_BACKSLASH:
1345 # if more than one line in this stmt already, just
1346 # mimic the current indent; else if initial line
1347 # has a start on an assignment stmt, indent to
1348 # beyond leftmost =; else to beyond first chunk of
1349 # non-whitespace on initial line
1350 if y.get_num_lines_in_stmt() > 1:
1351 text.insert("insert", indent)
1352 else:
1353 self.reindent_to(y.compute_backslash_indent())
1354 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001355 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001356 return "break"
1357
1358 # This line starts a brand new stmt; indent relative to
1359 # indentation of initial line of closest preceding
1360 # interesting stmt.
1361 indent = y.get_base_indent_string()
1362 text.insert("insert", indent)
1363 if y.is_block_opener():
1364 self.smart_indent_event(event)
1365 elif indent and y.is_block_closer():
1366 self.smart_backspace_event(event)
1367 return "break"
1368 finally:
1369 text.see("insert")
1370 text.undo_block_stop()
1371
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001372 # Our editwin provides a is_char_in_string function that works
1373 # with a Tk text index, but PyParse only knows about offsets into
1374 # a string. This builds a function for PyParse that accepts an
1375 # offset.
1376
1377 def _build_char_in_string_func(self, startindex):
1378 def inner(offset, _startindex=startindex,
1379 _icis=self.is_char_in_string):
1380 return _icis(_startindex + "+%dc" % offset)
1381 return inner
1382
1383 def indent_region_event(self, event):
1384 head, tail, chars, lines = self.get_region()
1385 for pos in range(len(lines)):
1386 line = lines[pos]
1387 if line:
1388 raw, effective = classifyws(line, self.tabwidth)
1389 effective = effective + self.indentwidth
1390 lines[pos] = self._make_blanks(effective) + line[raw:]
1391 self.set_region(head, tail, chars, lines)
1392 return "break"
1393
1394 def dedent_region_event(self, event):
1395 head, tail, chars, lines = self.get_region()
1396 for pos in range(len(lines)):
1397 line = lines[pos]
1398 if line:
1399 raw, effective = classifyws(line, self.tabwidth)
1400 effective = max(effective - self.indentwidth, 0)
1401 lines[pos] = self._make_blanks(effective) + line[raw:]
1402 self.set_region(head, tail, chars, lines)
1403 return "break"
1404
1405 def comment_region_event(self, event):
1406 head, tail, chars, lines = self.get_region()
1407 for pos in range(len(lines) - 1):
1408 line = lines[pos]
1409 lines[pos] = '##' + line
1410 self.set_region(head, tail, chars, lines)
1411
1412 def uncomment_region_event(self, event):
1413 head, tail, chars, lines = self.get_region()
1414 for pos in range(len(lines)):
1415 line = lines[pos]
1416 if not line:
1417 continue
1418 if line[:2] == '##':
1419 line = line[2:]
1420 elif line[:1] == '#':
1421 line = line[1:]
1422 lines[pos] = line
1423 self.set_region(head, tail, chars, lines)
1424
1425 def tabify_region_event(self, event):
1426 head, tail, chars, lines = self.get_region()
1427 tabwidth = self._asktabwidth()
1428 for pos in range(len(lines)):
1429 line = lines[pos]
1430 if line:
1431 raw, effective = classifyws(line, tabwidth)
1432 ntabs, nspaces = divmod(effective, tabwidth)
1433 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1434 self.set_region(head, tail, chars, lines)
1435
1436 def untabify_region_event(self, event):
1437 head, tail, chars, lines = self.get_region()
1438 tabwidth = self._asktabwidth()
1439 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001440 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001441 self.set_region(head, tail, chars, lines)
1442
1443 def toggle_tabs_event(self, event):
1444 if self.askyesno(
1445 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001446 "Turn tabs " + ("on", "off")[self.usetabs] +
1447 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001448 ("will be", "remains at")[self.usetabs] + " 8." +
1449 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001450 parent=self.text):
1451 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001452 # Try to prevent inconsistent indentation.
1453 # User must change indent width manually after using tabs.
1454 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001455 return "break"
1456
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001457 # XXX this isn't bound to anything -- see tabwidth comments
1458## def change_tabwidth_event(self, event):
1459## new = self._asktabwidth()
1460## if new != self.tabwidth:
1461## self.tabwidth = new
1462## self.set_indentation_params(0, guess=0)
1463## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001464
1465 def change_indentwidth_event(self, event):
1466 new = self.askinteger(
1467 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001468 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001469 parent=self.text,
1470 initialvalue=self.indentwidth,
1471 minvalue=2,
1472 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001473 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001474 self.indentwidth = new
1475 return "break"
1476
1477 def get_region(self):
1478 text = self.text
1479 first, last = self.get_selection_indices()
1480 if first and last:
1481 head = text.index(first + " linestart")
1482 tail = text.index(last + "-1c lineend +1c")
1483 else:
1484 head = text.index("insert linestart")
1485 tail = text.index("insert lineend +1c")
1486 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001487 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001488 return head, tail, chars, lines
1489
1490 def set_region(self, head, tail, chars, lines):
1491 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001492 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001493 if newchars == chars:
1494 text.bell()
1495 return
1496 text.tag_remove("sel", "1.0", "end")
1497 text.mark_set("insert", head)
1498 text.undo_block_start()
1499 text.delete(head, tail)
1500 text.insert(head, newchars)
1501 text.undo_block_stop()
1502 text.tag_add("sel", head, "insert")
1503
1504 # Make string that displays as n leading blanks.
1505
1506 def _make_blanks(self, n):
1507 if self.usetabs:
1508 ntabs, nspaces = divmod(n, self.tabwidth)
1509 return '\t' * ntabs + ' ' * nspaces
1510 else:
1511 return ' ' * n
1512
1513 # Delete from beginning of line to insert point, then reinsert
1514 # column logical (meaning use tabs if appropriate) spaces.
1515
1516 def reindent_to(self, column):
1517 text = self.text
1518 text.undo_block_start()
1519 if text.compare("insert linestart", "!=", "insert"):
1520 text.delete("insert linestart", "insert")
1521 if column:
1522 text.insert("insert", self._make_blanks(column))
1523 text.undo_block_stop()
1524
1525 def _asktabwidth(self):
1526 return self.askinteger(
1527 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001528 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001529 parent=self.text,
1530 initialvalue=self.indentwidth,
1531 minvalue=2,
1532 maxvalue=16) or self.tabwidth
1533
1534 # Guess indentwidth from text content.
1535 # Return guessed indentwidth. This should not be believed unless
1536 # it's in a reasonable range (e.g., it will be 0 if no indented
1537 # blocks are found).
1538
1539 def guess_indent(self):
1540 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1541 if opener and indented:
1542 raw, indentsmall = classifyws(opener, self.tabwidth)
1543 raw, indentlarge = classifyws(indented, self.tabwidth)
1544 else:
1545 indentsmall = indentlarge = 0
1546 return indentlarge - indentsmall
1547
1548# "line.col" -> line, as an int
1549def index2line(index):
1550 return int(float(index))
1551
1552# Look at the leading whitespace in s.
1553# Return pair (# of leading ws characters,
1554# effective # of leading blanks after expanding
1555# tabs to width tabwidth)
1556
1557def classifyws(s, tabwidth):
1558 raw = effective = 0
1559 for ch in s:
1560 if ch == ' ':
1561 raw = raw + 1
1562 effective = effective + 1
1563 elif ch == '\t':
1564 raw = raw + 1
1565 effective = (effective // tabwidth + 1) * tabwidth
1566 else:
1567 break
1568 return raw, effective
1569
1570import tokenize
1571_tokenize = tokenize
1572del tokenize
1573
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001574class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001575
1576 # .run() chews over the Text widget, looking for a block opener
1577 # and the stmt following it. Returns a pair,
1578 # (line containing block opener, line containing stmt)
1579 # Either or both may be None.
1580
1581 def __init__(self, text, tabwidth):
1582 self.text = text
1583 self.tabwidth = tabwidth
1584 self.i = self.finished = 0
1585 self.blkopenline = self.indentedline = None
1586
1587 def readline(self):
1588 if self.finished:
1589 return ""
1590 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001591 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001592 if self.text.compare(mark, ">=", "end"):
1593 return ""
1594 return self.text.get(mark, mark + " lineend+1c")
1595
1596 def tokeneater(self, type, token, start, end, line,
1597 INDENT=_tokenize.INDENT,
1598 NAME=_tokenize.NAME,
1599 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1600 if self.finished:
1601 pass
1602 elif type == NAME and token in OPENERS:
1603 self.blkopenline = line
1604 elif type == INDENT and self.blkopenline:
1605 self.indentedline = line
1606 self.finished = 1
1607
1608 def run(self):
1609 save_tabsize = _tokenize.tabsize
1610 _tokenize.tabsize = self.tabwidth
1611 try:
1612 try:
1613 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001614 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001615 # since we cut off the tokenizer early, we can trigger
1616 # spurious errors
1617 pass
1618 finally:
1619 _tokenize.tabsize = save_tabsize
1620 return self.blkopenline, self.indentedline
1621
1622### end autoindent code ###
1623
David Scherer7aced172000-08-15 01:13:23 +00001624def prepstr(s):
1625 # Helper to extract the underscore from a string, e.g.
1626 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001627 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001628 if i >= 0:
1629 s = s[:i] + s[i+1:]
1630 return i, s
1631
1632
1633keynames = {
1634 'bracketleft': '[',
1635 'bracketright': ']',
1636 'slash': '/',
1637}
1638
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001639def get_accelerator(keydefs, eventname):
1640 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001641 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1642 # if not keylist:
1643 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1644 "<<open-module>>",
1645 "<<goto-line>>",
1646 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001647 return ""
1648 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001649 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001650 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1651 s = re.sub("Key-", "", s)
1652 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1653 s = re.sub("Control-", "Ctrl-", s)
1654 s = re.sub("-", "+", s)
1655 s = re.sub("><", " ", s)
1656 s = re.sub("<", "", s)
1657 s = re.sub(">", "", s)
1658 return s
1659
1660
1661def fixwordbreaks(root):
1662 # Make sure that Tk's double-click and next/previous word
1663 # operations use our definition of a word (i.e. an identifier)
1664 tk = root.tk
1665 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1666 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1667 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1668
1669
1670def test():
1671 root = Tk()
1672 fixwordbreaks(root)
1673 root.withdraw()
1674 if sys.argv[1:]:
1675 filename = sys.argv[1]
1676 else:
1677 filename = None
1678 edit = EditorWindow(root=root, filename=filename)
1679 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001680 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001681 root.mainloop()
1682 root.destroy()
1683
1684if __name__ == '__main__':
1685 test()