blob: 241bd38ff9c3d6f0b5df12b528ae9e6371f78e11 [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')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000175 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000176 text_options = {
177 'name': 'text',
178 'padx': 5,
179 'wrap': 'none',
180 'width': self.width,
181 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
182 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'),
258 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
259 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000260 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
261 text.pack(side=TOP, fill=BOTH, expand=1)
262 text.focus_set()
263
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000264 # usetabs true -> literal tab characters are used by indent and
265 # dedent cmds, possibly mixed with spaces if
266 # indentwidth is not a multiple of tabwidth,
267 # which will cause Tabnanny to nag!
268 # false -> tab characters are converted to spaces by indent
269 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000270 # Although use-spaces=0 can be configured manually in config-main.def,
271 # configuration of tabs v. spaces is not supported in the configuration
272 # dialog. IDLE promotes the preferred Python indentation: use spaces!
273 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
274 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000275
276 # tabwidth is the display width of a literal tab character.
277 # CAUTION: telling Tk to use anything other than its default
278 # tab setting causes it to use an entirely different tabbing algorithm,
279 # treating tab stops as fixed distances from the left margin.
280 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000281 self.tabwidth = 8 # must remain 8 until Tk is fixed.
282
283 # indentwidth is the number of screen characters per indent level.
284 # The recommended Python indentation is four spaces.
285 self.indentwidth = self.tabwidth
286 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000287
288 # If context_use_ps1 is true, parsing searches back for a ps1 line;
289 # else searches for a popular (if, def, ...) Python stmt.
290 self.context_use_ps1 = False
291
292 # When searching backwards for a reliable place to begin parsing,
293 # first start num_context_lines[0] lines back, then
294 # num_context_lines[1] lines back if that didn't work, and so on.
295 # The last value should be huge (larger than the # of lines in a
296 # conceivable file).
297 # Making the initial values larger slows things down more often.
298 self.num_context_lines = 50, 500, 5000000
299
David Scherer7aced172000-08-15 01:13:23 +0000300 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000301
302 self.undo = undo = self.UndoDelegator()
303 per.insertfilter(undo)
304 text.undo_block_start = undo.undo_block_start
305 text.undo_block_stop = undo.undo_block_stop
306 undo.set_saved_change_hook(self.saved_change_hook)
307
308 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000309 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000310 io.set_filename_change_hook(self.filename_change_hook)
311
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000312 # Create the recent files submenu
313 self.recent_files_menu = Menu(self.menubar)
314 self.menudict['file'].insert_cascade(3, label='Recent Files',
315 underline=0,
316 menu=self.recent_files_menu)
317 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000318
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000319 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000320 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000321 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000322 io.loadfile(filename)
323 else:
324 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000325 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000326 self.saved_change_hook()
327
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000328 self.set_indentation_params(self.ispythonsource(filename))
329
David Scherer7aced172000-08-15 01:13:23 +0000330 self.load_extensions()
331
332 menu = self.menudict.get('windows')
333 if menu:
334 end = menu.index("end")
335 if end is None:
336 end = -1
337 if end >= 0:
338 menu.add_separator()
339 end = end + 1
340 self.wmenu_end = end
341 WindowList.register_callback(self.postwindowsmenu)
342
343 # Some abstractions so IDLE extensions are cross-IDE
344 self.askyesno = tkMessageBox.askyesno
345 self.askinteger = tkSimpleDialog.askinteger
346 self.showerror = tkMessageBox.showerror
347
Martin v. Löwis307021f2005-11-27 16:59:04 +0000348 def _filename_to_unicode(self, filename):
349 """convert filename to unicode in order to display it in Tk"""
350 if isinstance(filename, unicode) or not filename:
351 return filename
352 else:
353 try:
354 return filename.decode(self.filesystemencoding)
355 except UnicodeDecodeError:
356 # XXX
357 try:
358 return filename.decode(self.encoding)
359 except UnicodeDecodeError:
360 # byte-to-byte conversion
361 return filename.decode('iso8859-1')
362
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000363 def new_callback(self, event):
364 dirname, basename = self.io.defaultfilename()
365 self.flist.new(dirname)
366 return "break"
367
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000368 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400369 if (event.state & 4) != 0 and event.keysym == "Home":
370 # state&4==Control. If <Control-Home>, use the Tk binding.
371 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000372 if self.text.index("iomark") and \
373 self.text.compare("iomark", "<=", "insert lineend") and \
374 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400375 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000376 insertpt = int(self.text.index("iomark").split(".")[1])
377 else:
378 line = self.text.get("insert linestart", "insert lineend")
379 for insertpt in xrange(len(line)):
380 if line[insertpt] not in (' ','\t'):
381 break
382 else:
383 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000384 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000385 if insertpt == lineat:
386 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000387 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000388 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400389 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000390 self.text.tag_remove("sel", "1.0", "end")
391 else:
392 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400393 self.text.mark_set("my_anchor", "insert") # there was no previous selection
394 else:
395 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
396 self.text.mark_set("my_anchor", "sel.first") # extend back
397 else:
398 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000399 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400400 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000401 if self.text.compare(first,">",last):
402 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000403 self.text.tag_remove("sel", "1.0", "end")
404 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000405 self.text.mark_set("insert", dest)
406 self.text.see("insert")
407 return "break"
408
David Scherer7aced172000-08-15 01:13:23 +0000409 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000410 self.status_bar = self.MultiStatusBar(self.top)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000411 if macosxSupport.runningAsOSXApp():
412 # Insert some padding to avoid obscuring some of the statusbar
413 # by the resize widget.
414 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000415 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
416 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
417 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000418 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
419 self.text.event_add("<<set-line-and-column>>",
420 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000421 self.text.after_idle(self.set_line_and_column)
422
423 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000424 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000425 self.status_bar.set_label('column', 'Col: %s' % column)
426 self.status_bar.set_label('line', 'Ln: %s' % line)
427
David Scherer7aced172000-08-15 01:13:23 +0000428 menu_specs = [
429 ("file", "_File"),
430 ("edit", "_Edit"),
431 ("format", "F_ormat"),
432 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000433 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000434 ("windows", "_Windows"),
435 ("help", "_Help"),
436 ]
437
Ronald Oussoren19302d92006-06-11 14:33:36 +0000438 if macosxSupport.runningAsOSXApp():
439 del menu_specs[-3]
440 menu_specs[-2] = ("windows", "_Window")
441
442
David Scherer7aced172000-08-15 01:13:23 +0000443 def createmenubar(self):
444 mbar = self.menubar
445 self.menudict = menudict = {}
446 for name, label in self.menu_specs:
447 underline, label = prepstr(label)
448 menudict[name] = menu = Menu(mbar, name=name)
449 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000450
Ned Deily4a705502011-01-18 04:33:22 +0000451 if macosxSupport.isCarbonAquaTk(self.root):
Ronald Oussoren19302d92006-06-11 14:33:36 +0000452 # Insert the application menu
453 menudict['application'] = menu = Menu(mbar, name='apple')
454 mbar.add_cascade(label='IDLE', menu=menu)
455
David Scherer7aced172000-08-15 01:13:23 +0000456 self.fill_menus()
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 Svetlov5018db72012-11-01 22:39:14 +0200481
482 for label, eventname, verify_state in self.rmenu_specs:
483 if verify_state is None:
484 continue
485 state = getattr(self, verify_state)()
486 rmenu.entryconfigure(label, state=state)
487
David Scherer7aced172000-08-15 01:13:23 +0000488 rmenu.tk_popup(event.x_root, event.y_root)
489 if iswin:
490 self.text.config(cursor="ibeam")
491
492 rmenu_specs = [
Andrew Svetlov5018db72012-11-01 22:39:14 +0200493 # ("Label", "<<virtual-event>>", "statefuncname"), ...
494 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000495 ]
496
497 def make_rmenu(self):
498 rmenu = Menu(self.text, tearoff=0)
Andrew Svetlov5018db72012-11-01 22:39:14 +0200499 for label, eventname, _ in self.rmenu_specs:
500 if label is not None:
501 def command(text=self.text, eventname=eventname):
502 text.event_generate(eventname)
503 rmenu.add_command(label=label, command=command)
504 else:
505 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000506 self.rmenu = rmenu
507
Andrew Svetlov5018db72012-11-01 22:39:14 +0200508 def rmenu_check_cut(self):
509 return self.rmenu_check_copy()
510
511 def rmenu_check_copy(self):
512 try:
513 indx = self.text.index('sel.first')
514 except TclError:
515 return 'disabled'
516 else:
517 return 'normal' if indx else 'disabled'
518
519 def rmenu_check_paste(self):
520 try:
521 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
522 except TclError:
523 return 'disabled'
524 else:
525 return 'normal'
526
David Scherer7aced172000-08-15 01:13:23 +0000527 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000528 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000529
Steven M. Gava3b55a892001-11-21 05:56:26 +0000530 def config_dialog(self, event=None):
531 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000532
David Scherer7aced172000-08-15 01:13:23 +0000533 def help_dialog(self, event=None):
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500534 if self.root:
535 parent = self.root
536 else:
537 parent = self.top
538 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000539
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000540 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000541 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000542 try:
543 os.startfile(self.help_url)
544 except WindowsError as why:
545 tkMessageBox.showerror(title='Document Start Failure',
546 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000547 else:
548 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000549 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000550
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000551 def cut(self,event):
552 self.text.event_generate("<<Cut>>")
553 return "break"
554
555 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000556 if not self.text.tag_ranges("sel"):
557 # There is no selection, so do nothing and maybe interrupt.
558 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000559 self.text.event_generate("<<Copy>>")
560 return "break"
561
562 def paste(self,event):
563 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000564 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000565 return "break"
566
David Scherer7aced172000-08-15 01:13:23 +0000567 def select_all(self, event=None):
568 self.text.tag_add("sel", "1.0", "end-1c")
569 self.text.mark_set("insert", "1.0")
570 self.text.see("insert")
571 return "break"
572
573 def remove_selection(self, event=None):
574 self.text.tag_remove("sel", "1.0", "end")
575 self.text.see("insert")
576
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000577 def move_at_edge_if_selection(self, edge_index):
578 """Cursor move begins at start or end of selection
579
580 When a left/right cursor key is pressed create and return to Tkinter a
581 function which causes a cursor move from the associated edge of the
582 selection.
583
584 """
585 self_text_index = self.text.index
586 self_text_mark_set = self.text.mark_set
587 edges_table = ("sel.first+1c", "sel.last-1c")
588 def move_at_edge(event):
589 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
590 try:
591 self_text_index("sel.first")
592 self_text_mark_set("insert", edges_table[edge_index])
593 except TclError:
594 pass
595 return move_at_edge
596
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000597 def del_word_left(self, event):
598 self.text.event_generate('<Meta-Delete>')
599 return "break"
600
601 def del_word_right(self, event):
602 self.text.event_generate('<Meta-d>')
603 return "break"
604
Steven M. Gavac5976402002-01-04 03:06:08 +0000605 def find_event(self, event):
606 SearchDialog.find(self.text)
607 return "break"
608
609 def find_again_event(self, event):
610 SearchDialog.find_again(self.text)
611 return "break"
612
613 def find_selection_event(self, event):
614 SearchDialog.find_selection(self.text)
615 return "break"
616
617 def find_in_files_event(self, event):
618 GrepDialog.grep(self.text, self.io, self.flist)
619 return "break"
620
621 def replace_event(self, event):
622 ReplaceDialog.replace(self.text)
623 return "break"
624
625 def goto_line_event(self, event):
626 text = self.text
627 lineno = tkSimpleDialog.askinteger("Goto",
628 "Go to line number:",parent=text)
629 if lineno is None:
630 return "break"
631 if lineno <= 0:
632 text.bell()
633 return "break"
634 text.mark_set("insert", "%d.0" % lineno)
635 text.see("insert")
636
David Scherer7aced172000-08-15 01:13:23 +0000637 def open_module(self, event=None):
638 # XXX Shouldn't this be in IOBinding or in FileList?
639 try:
640 name = self.text.get("sel.first", "sel.last")
641 except TclError:
642 name = ""
643 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000644 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000645 name = tkSimpleDialog.askstring("Module",
646 "Enter the name of a Python module\n"
647 "to search on sys.path and open:",
648 parent=self.text, initialvalue=name)
649 if name:
650 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000651 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000652 return
David Scherer7aced172000-08-15 01:13:23 +0000653 # XXX Ought to insert current file's directory in front of path
654 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000655 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000656 except (NameError, ImportError), msg:
657 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
658 return
659 if type != imp.PY_SOURCE:
660 tkMessageBox.showerror("Unsupported type",
661 "%s is not a source module" % name, parent=self.text)
662 return
663 if f:
664 f.close()
665 if self.flist:
666 self.flist.open(file)
667 else:
668 self.io.loadfile(file)
669
670 def open_class_browser(self, event=None):
671 filename = self.io.filename
672 if not filename:
673 tkMessageBox.showerror(
674 "No filename",
675 "This buffer has no associated filename",
676 master=self.text)
677 self.text.focus_set()
678 return None
679 head, tail = os.path.split(filename)
680 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000681 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000682 ClassBrowser.ClassBrowser(self.flist, base, [head])
683
684 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000685 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000686 PathBrowser.PathBrowser(self.flist)
687
688 def gotoline(self, lineno):
689 if lineno is not None and lineno > 0:
690 self.text.mark_set("insert", "%d.0" % lineno)
691 self.text.tag_remove("sel", "1.0", "end")
692 self.text.tag_add("sel", "insert", "insert +1l")
693 self.center()
694
695 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000696 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000697 return True
David Scherer7aced172000-08-15 01:13:23 +0000698 base, ext = os.path.splitext(os.path.basename(filename))
699 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000700 return True
David Scherer7aced172000-08-15 01:13:23 +0000701 try:
702 f = open(filename)
703 line = f.readline()
704 f.close()
705 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000706 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000707 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000708
709 def close_hook(self):
710 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000711 self.flist.unregister_maybe_terminate(self)
712 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000713
714 def set_close_hook(self, close_hook):
715 self.close_hook = close_hook
716
717 def filename_change_hook(self):
718 if self.flist:
719 self.flist.filename_changed_edit(self)
720 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000721 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000722 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000723
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000724 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000725 if self.color:
726 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000727 if self.ispythonsource(self.io.filename):
728 self.color = self.ColorDelegator()
729 # can add more colorizers here...
730 if self.color:
731 self.per.removefilter(self.undo)
732 self.per.insertfilter(self.color)
733 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000734
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000735 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000736 if not self.color:
737 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000738 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000739 self.per.removefilter(self.color)
740 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000741
Steven M. Gavab77d3432002-03-02 07:16:21 +0000742 def ResetColorizer(self):
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000743 "Update the colour theme"
744 # Called from self.filename_change_hook and from configDialog.py
745 self._rmcolorizer()
746 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000747 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000748 normal_colors = idleConf.GetHighlight(theme, 'normal')
749 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
750 select_colors = idleConf.GetHighlight(theme, 'hilite')
751 self.text.config(
752 foreground=normal_colors['foreground'],
753 background=normal_colors['background'],
754 insertbackground=cursor_color,
755 selectforeground=select_colors['foreground'],
756 selectbackground=select_colors['background'],
757 )
David Scherer7aced172000-08-15 01:13:23 +0000758
Steven M. Gavab1585412002-03-12 00:21:56 +0000759 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000760 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000761 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000762 fontWeight='normal'
763 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
764 fontWeight='bold'
765 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
766 idleConf.GetOption('main','EditorWindow','font-size'),
767 fontWeight))
768
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000769 def RemoveKeybindings(self):
770 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000771 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000772 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000773 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000774 self.text.event_delete(event, *keylist)
775 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000776 xkeydefs = idleConf.GetExtensionBindings(extensionName)
777 if xkeydefs:
778 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000779 self.text.event_delete(event, *keylist)
780
781 def ApplyKeybindings(self):
782 "Update the keybindings after they are changed"
783 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000784 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000785 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000786 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000787 xkeydefs = idleConf.GetExtensionBindings(extensionName)
788 if xkeydefs:
789 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000790 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000791 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000792 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000793 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000794 for item in menu[1]:
795 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000796 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000797 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000798 menu = self.menudict[menubarItem]
799 end = menu.index(END) + 1
800 for index in range(0, end):
801 if menu.type(index) == 'command':
802 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000803 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000804 itemName = menu.entrycget(index, 'label')
805 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000806 if menubarItem in menuEventDict:
807 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000808 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000809 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000810 accel = get_accelerator(keydefs, event)
811 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000812
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000813 def set_notabs_indentwidth(self):
814 "Update the indentwidth if changed and not using tabs in this window"
815 # Called from configDialog.py
816 if not self.usetabs:
817 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
818 type='int')
819
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000820 def reset_help_menu_entries(self):
821 "Update the additional help entries on the Help menu"
822 help_list = idleConf.GetAllExtraHelpSourcesList()
823 helpmenu = self.menudict['help']
824 # first delete the extra help entries, if any
825 helpmenu_length = helpmenu.index(END)
826 if helpmenu_length > self.base_helpmenu_length:
827 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
828 # then rebuild them
829 if help_list:
830 helpmenu.add_separator()
831 for entry in help_list:
832 cmd = self.__extra_help_callback(entry[1])
833 helpmenu.add_command(label=entry[0], command=cmd)
834 # and update the menu dictionary
835 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000836
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000837 def __extra_help_callback(self, helpfile):
838 "Create a callback with the helpfile value frozen at definition time"
839 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000840 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000841 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000842 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000843 try:
844 os.startfile(helpfile)
845 except WindowsError as why:
846 tkMessageBox.showerror(title='Document Start Failure',
847 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000848 else:
849 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000850 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000851
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000852 def update_recent_files_list(self, new_file=None):
853 "Load and update the recent files list and menus"
854 rf_list = []
855 if os.path.exists(self.recent_files_path):
856 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000857 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000858 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000859 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000860 rf_list_file.close()
861 if new_file:
862 new_file = os.path.abspath(new_file) + '\n'
863 if new_file in rf_list:
864 rf_list.remove(new_file) # move to top
865 rf_list.insert(0, new_file)
866 # clean and save the recent files list
867 bad_paths = []
868 for path in rf_list:
869 if '\0' in path or not os.path.exists(path[0:-1]):
870 bad_paths.append(path)
871 rf_list = [path for path in rf_list if path not in bad_paths]
872 ulchars = "1234567890ABCDEFGHIJK"
873 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000874 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800875 with open(self.recent_files_path, 'w') as rf_file:
876 rf_file.writelines(rf_list)
877 except IOError as err:
878 if not getattr(self.root, "recentfilelist_error_displayed", False):
879 self.root.recentfilelist_error_displayed = True
880 tkMessageBox.showerror(title='IDLE Error',
881 message='Unable to update Recent Files list:\n%s'
882 % str(err),
883 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000884 # for each edit window instance, construct the recent files menu
885 for instance in self.top.instance_dict.keys():
886 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700887 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000888 for i, file_name in enumerate(rf_list):
889 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000890 # make unicode string to display non-ASCII chars correctly
891 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000892 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000893 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000894 command=callback,
895 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000896
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000897 def __recent_file_callback(self, file_name):
898 def open_recent_file(fn_closure=file_name):
899 self.io.open(editFile=fn_closure)
900 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000901
David Scherer7aced172000-08-15 01:13:23 +0000902 def saved_change_hook(self):
903 short = self.short_title()
904 long = self.long_title()
905 if short and long:
906 title = short + " - " + long
907 elif short:
908 title = short
909 elif long:
910 title = long
911 else:
912 title = "Untitled"
913 icon = short or long or title
914 if not self.get_saved():
915 title = "*%s*" % title
916 icon = "*%s" % icon
917 self.top.wm_title(title)
918 self.top.wm_iconname(icon)
919
920 def get_saved(self):
921 return self.undo.get_saved()
922
923 def set_saved(self, flag):
924 self.undo.set_saved(flag)
925
926 def reset_undo(self):
927 self.undo.reset_undo()
928
929 def short_title(self):
930 filename = self.io.filename
931 if filename:
932 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000933 # return unicode string to display non-ASCII chars correctly
934 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000935
936 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000937 # return unicode string to display non-ASCII chars correctly
938 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000939
940 def center_insert_event(self, event):
941 self.center()
942
943 def center(self, mark="insert"):
944 text = self.text
945 top, bot = self.getwindowlines()
946 lineno = self.getlineno(mark)
947 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000948 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000949 text.yview(float(newtop))
950
951 def getwindowlines(self):
952 text = self.text
953 top = self.getlineno("@0,0")
954 bot = self.getlineno("@0,65535")
955 if top == bot and text.winfo_height() == 1:
956 # Geometry manager hasn't run yet
957 height = int(text['height'])
958 bot = top + height - 1
959 return top, bot
960
961 def getlineno(self, mark="insert"):
962 text = self.text
963 return int(float(text.index(mark)))
964
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000965 def get_geometry(self):
966 "Return (width, height, x, y)"
967 geom = self.top.wm_geometry()
968 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
969 tuple = (map(int, m.groups()))
970 return tuple
971
David Scherer7aced172000-08-15 01:13:23 +0000972 def close_event(self, event):
973 self.close()
974
975 def maybesave(self):
976 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000977 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000978 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000979 self.top.deiconify()
980 self.top.lower()
981 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000982 return self.io.maybesave()
983
984 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000985 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +0000986 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000987 self._close()
988 return reply
989
990 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000991 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000992 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000993 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000994 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000995 self.io.close()
996 self.io = None
997 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000998 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000999 self.color.close(False)
1000 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001001 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001002 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001003 self.per.close()
1004 self.per = None
1005 self.top.destroy()
1006 if self.close_hook:
1007 # unless override: unregister from flist, terminate if last window
1008 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001009
1010 def load_extensions(self):
1011 self.extensions = {}
1012 self.load_standard_extensions()
1013
1014 def unload_extensions(self):
1015 for ins in self.extensions.values():
1016 if hasattr(ins, "close"):
1017 ins.close()
1018 self.extensions = {}
1019
1020 def load_standard_extensions(self):
1021 for name in self.get_standard_extension_names():
1022 try:
1023 self.load_extension(name)
1024 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001025 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001026 import traceback
1027 traceback.print_exc()
1028
1029 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001030 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001031
1032 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001033 try:
1034 mod = __import__(name, globals(), locals(), [])
1035 except ImportError:
1036 print "\nFailed to import extension: ", name
1037 return
David Scherer7aced172000-08-15 01:13:23 +00001038 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001039 keydefs = idleConf.GetExtensionBindings(name)
1040 if hasattr(cls, "menudefs"):
1041 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001042 ins = cls(self)
1043 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001044 if keydefs:
1045 self.apply_bindings(keydefs)
1046 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001047 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001048 while methodname[:1] == '<':
1049 methodname = methodname[1:]
1050 while methodname[-1:] == '>':
1051 methodname = methodname[:-1]
1052 methodname = methodname + "_event"
1053 if hasattr(ins, methodname):
1054 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001055
1056 def apply_bindings(self, keydefs=None):
1057 if keydefs is None:
1058 keydefs = self.Bindings.default_keydefs
1059 text = self.text
1060 text.keydefs = keydefs
1061 for event, keylist in keydefs.items():
1062 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001063 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001064
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001065 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001066 """Add appropriate entries to the menus and submenus
1067
1068 Menus that are absent or None in self.menudict are ignored.
1069 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001070 if menudefs is None:
1071 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001072 if keydefs is None:
1073 keydefs = self.Bindings.default_keydefs
1074 menudict = self.menudict
1075 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001076 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001077 menu = menudict.get(mname)
1078 if not menu:
1079 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001080 for entry in entrylist:
1081 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001082 menu.add_separator()
1083 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001084 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001085 checkbutton = (label[:1] == '!')
1086 if checkbutton:
1087 label = label[1:]
1088 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001089 accelerator = get_accelerator(keydefs, eventname)
1090 def command(text=text, eventname=eventname):
1091 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001092 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001093 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001094 menu.add_checkbutton(label=label, underline=underline,
1095 command=command, accelerator=accelerator,
1096 variable=var)
1097 else:
1098 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001099 command=command,
1100 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001101
1102 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001103 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001104 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001105 value = var.get()
1106 return value
1107 else:
1108 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001109
1110 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001111 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001112 if var:
1113 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001114 else:
1115 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001116
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001117 def get_var_obj(self, name, vartype=None):
1118 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001119 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001120 # create a Tkinter variable object with self.text as master:
1121 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001122 return var
1123
1124 # Tk implementations of "virtual text methods" -- each platform
1125 # reusing IDLE's support code needs to define these for its GUI's
1126 # flavor of widget.
1127
1128 # Is character at text_index in a Python string? Return 0 for
1129 # "guaranteed no", true for anything else. This info is expensive
1130 # to compute ab initio, but is probably already known by the
1131 # platform's colorizer.
1132
1133 def is_char_in_string(self, text_index):
1134 if self.color:
1135 # Return true iff colorizer hasn't (re)gotten this far
1136 # yet, or the character is tagged as being in a string
1137 return self.text.tag_prevrange("TODO", text_index) or \
1138 "STRING" in self.text.tag_names(text_index)
1139 else:
1140 # The colorizer is missing: assume the worst
1141 return 1
1142
1143 # If a selection is defined in the text widget, return (start,
1144 # end) as Tkinter text indices, otherwise return (None, None)
1145 def get_selection_indices(self):
1146 try:
1147 first = self.text.index("sel.first")
1148 last = self.text.index("sel.last")
1149 return first, last
1150 except TclError:
1151 return None, None
1152
1153 # Return the text widget's current view of what a tab stop means
1154 # (equivalent width in spaces).
1155
1156 def get_tabwidth(self):
1157 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1158 return int(current)
1159
1160 # Set the text widget's current view of what a tab stop means.
1161
1162 def set_tabwidth(self, newtabwidth):
1163 text = self.text
1164 if self.get_tabwidth() != newtabwidth:
1165 pixels = text.tk.call("font", "measure", text["font"],
1166 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001167 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001168 text.configure(tabs=pixels)
1169
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001170 # If ispythonsource and guess are true, guess a good value for
1171 # indentwidth based on file content (if possible), and if
1172 # indentwidth != tabwidth set usetabs false.
1173 # In any case, adjust the Text widget's view of what a tab
1174 # character means.
1175
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001176 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001177 if guess and ispythonsource:
1178 i = self.guess_indent()
1179 if 2 <= i <= 8:
1180 self.indentwidth = i
1181 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001182 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001183 self.set_tabwidth(self.tabwidth)
1184
1185 def smart_backspace_event(self, event):
1186 text = self.text
1187 first, last = self.get_selection_indices()
1188 if first and last:
1189 text.delete(first, last)
1190 text.mark_set("insert", first)
1191 return "break"
1192 # Delete whitespace left, until hitting a real char or closest
1193 # preceding virtual tab stop.
1194 chars = text.get("insert linestart", "insert")
1195 if chars == '':
1196 if text.compare("insert", ">", "1.0"):
1197 # easy: delete preceding newline
1198 text.delete("insert-1c")
1199 else:
1200 text.bell() # at start of buffer
1201 return "break"
1202 if chars[-1] not in " \t":
1203 # easy: delete preceding real char
1204 text.delete("insert-1c")
1205 return "break"
1206 # Ick. It may require *inserting* spaces if we back up over a
1207 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001208 tabwidth = self.tabwidth
1209 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001210 assert have > 0
1211 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001212 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001213 if self.context_use_ps1:
1214 last_line_of_prompt = sys.ps1.split('\n')[-1]
1215 else:
1216 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001217 ncharsdeleted = 0
1218 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001219 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001220 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001221 chars = chars[:-1]
1222 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001223 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001224 if have <= want or chars[-1] not in " \t":
1225 break
1226 text.undo_block_start()
1227 text.delete("insert-%dc" % ncharsdeleted, "insert")
1228 if have < want:
1229 text.insert("insert", ' ' * (want - have))
1230 text.undo_block_stop()
1231 return "break"
1232
1233 def smart_indent_event(self, event):
1234 # if intraline selection:
1235 # delete it
1236 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001237 # do indent-region
1238 # else:
1239 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001240 text = self.text
1241 first, last = self.get_selection_indices()
1242 text.undo_block_start()
1243 try:
1244 if first and last:
1245 if index2line(first) != index2line(last):
1246 return self.indent_region_event(event)
1247 text.delete(first, last)
1248 text.mark_set("insert", first)
1249 prefix = text.get("insert linestart", "insert")
1250 raw, effective = classifyws(prefix, self.tabwidth)
1251 if raw == len(prefix):
1252 # only whitespace to the left
1253 self.reindent_to(effective + self.indentwidth)
1254 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001255 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001256 if self.usetabs:
1257 pad = '\t'
1258 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001259 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001260 n = self.indentwidth
1261 pad = ' ' * (n - effective % n)
1262 text.insert("insert", pad)
1263 text.see("insert")
1264 return "break"
1265 finally:
1266 text.undo_block_stop()
1267
1268 def newline_and_indent_event(self, event):
1269 text = self.text
1270 first, last = self.get_selection_indices()
1271 text.undo_block_start()
1272 try:
1273 if first and last:
1274 text.delete(first, last)
1275 text.mark_set("insert", first)
1276 line = text.get("insert linestart", "insert")
1277 i, n = 0, len(line)
1278 while i < n and line[i] in " \t":
1279 i = i+1
1280 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001281 # the cursor is in or at leading indentation in a continuation
1282 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001283 text.insert("insert linestart", '\n')
1284 return "break"
1285 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001286 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001287 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001288 last_line_of_prompt = sys.ps1.split('\n')[-1]
1289 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001290 line = line[:-1]
1291 i = i+1
1292 if i:
1293 text.delete("insert - %d chars" % i, "insert")
1294 # strip whitespace after insert point
1295 while text.get("insert") in " \t":
1296 text.delete("insert")
1297 # start new line
1298 text.insert("insert", '\n')
1299
1300 # adjust indentation for continuations and block
1301 # open/close first need to find the last stmt
1302 lno = index2line(text.index('insert'))
1303 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001304 if not self.context_use_ps1:
1305 for context in self.num_context_lines:
1306 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001307 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001308 rawtext = text.get(startatindex, "insert")
1309 y.set_str(rawtext)
1310 bod = y.find_good_parse_start(
1311 self.context_use_ps1,
1312 self._build_char_in_string_func(startatindex))
1313 if bod is not None or startat == 1:
1314 break
1315 y.set_lo(bod or 0)
1316 else:
1317 r = text.tag_prevrange("console", "insert")
1318 if r:
1319 startatindex = r[1]
1320 else:
1321 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001322 rawtext = text.get(startatindex, "insert")
1323 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001324 y.set_lo(0)
1325
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001326 c = y.get_continuation_type()
1327 if c != PyParse.C_NONE:
1328 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001329 if c == PyParse.C_STRING_FIRST_LINE:
1330 # after the first line of a string; do not indent at all
1331 pass
1332 elif c == PyParse.C_STRING_NEXT_LINES:
1333 # inside a string which started before this line;
1334 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001335 text.insert("insert", indent)
1336 elif c == PyParse.C_BRACKET:
1337 # line up with the first (if any) element of the
1338 # last open bracket structure; else indent one
1339 # level beyond the indent of the line with the
1340 # last open bracket
1341 self.reindent_to(y.compute_bracket_indent())
1342 elif c == PyParse.C_BACKSLASH:
1343 # if more than one line in this stmt already, just
1344 # mimic the current indent; else if initial line
1345 # has a start on an assignment stmt, indent to
1346 # beyond leftmost =; else to beyond first chunk of
1347 # non-whitespace on initial line
1348 if y.get_num_lines_in_stmt() > 1:
1349 text.insert("insert", indent)
1350 else:
1351 self.reindent_to(y.compute_backslash_indent())
1352 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001353 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001354 return "break"
1355
1356 # This line starts a brand new stmt; indent relative to
1357 # indentation of initial line of closest preceding
1358 # interesting stmt.
1359 indent = y.get_base_indent_string()
1360 text.insert("insert", indent)
1361 if y.is_block_opener():
1362 self.smart_indent_event(event)
1363 elif indent and y.is_block_closer():
1364 self.smart_backspace_event(event)
1365 return "break"
1366 finally:
1367 text.see("insert")
1368 text.undo_block_stop()
1369
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001370 # Our editwin provides a is_char_in_string function that works
1371 # with a Tk text index, but PyParse only knows about offsets into
1372 # a string. This builds a function for PyParse that accepts an
1373 # offset.
1374
1375 def _build_char_in_string_func(self, startindex):
1376 def inner(offset, _startindex=startindex,
1377 _icis=self.is_char_in_string):
1378 return _icis(_startindex + "+%dc" % offset)
1379 return inner
1380
1381 def indent_region_event(self, event):
1382 head, tail, chars, lines = self.get_region()
1383 for pos in range(len(lines)):
1384 line = lines[pos]
1385 if line:
1386 raw, effective = classifyws(line, self.tabwidth)
1387 effective = effective + self.indentwidth
1388 lines[pos] = self._make_blanks(effective) + line[raw:]
1389 self.set_region(head, tail, chars, lines)
1390 return "break"
1391
1392 def dedent_region_event(self, event):
1393 head, tail, chars, lines = self.get_region()
1394 for pos in range(len(lines)):
1395 line = lines[pos]
1396 if line:
1397 raw, effective = classifyws(line, self.tabwidth)
1398 effective = max(effective - self.indentwidth, 0)
1399 lines[pos] = self._make_blanks(effective) + line[raw:]
1400 self.set_region(head, tail, chars, lines)
1401 return "break"
1402
1403 def comment_region_event(self, event):
1404 head, tail, chars, lines = self.get_region()
1405 for pos in range(len(lines) - 1):
1406 line = lines[pos]
1407 lines[pos] = '##' + line
1408 self.set_region(head, tail, chars, lines)
1409
1410 def uncomment_region_event(self, event):
1411 head, tail, chars, lines = self.get_region()
1412 for pos in range(len(lines)):
1413 line = lines[pos]
1414 if not line:
1415 continue
1416 if line[:2] == '##':
1417 line = line[2:]
1418 elif line[:1] == '#':
1419 line = line[1:]
1420 lines[pos] = line
1421 self.set_region(head, tail, chars, lines)
1422
1423 def tabify_region_event(self, event):
1424 head, tail, chars, lines = self.get_region()
1425 tabwidth = self._asktabwidth()
1426 for pos in range(len(lines)):
1427 line = lines[pos]
1428 if line:
1429 raw, effective = classifyws(line, tabwidth)
1430 ntabs, nspaces = divmod(effective, tabwidth)
1431 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1432 self.set_region(head, tail, chars, lines)
1433
1434 def untabify_region_event(self, event):
1435 head, tail, chars, lines = self.get_region()
1436 tabwidth = self._asktabwidth()
1437 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001438 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001439 self.set_region(head, tail, chars, lines)
1440
1441 def toggle_tabs_event(self, event):
1442 if self.askyesno(
1443 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001444 "Turn tabs " + ("on", "off")[self.usetabs] +
1445 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001446 ("will be", "remains at")[self.usetabs] + " 8." +
1447 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001448 parent=self.text):
1449 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001450 # Try to prevent inconsistent indentation.
1451 # User must change indent width manually after using tabs.
1452 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001453 return "break"
1454
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001455 # XXX this isn't bound to anything -- see tabwidth comments
1456## def change_tabwidth_event(self, event):
1457## new = self._asktabwidth()
1458## if new != self.tabwidth:
1459## self.tabwidth = new
1460## self.set_indentation_params(0, guess=0)
1461## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001462
1463 def change_indentwidth_event(self, event):
1464 new = self.askinteger(
1465 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001466 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001467 parent=self.text,
1468 initialvalue=self.indentwidth,
1469 minvalue=2,
1470 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001471 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001472 self.indentwidth = new
1473 return "break"
1474
1475 def get_region(self):
1476 text = self.text
1477 first, last = self.get_selection_indices()
1478 if first and last:
1479 head = text.index(first + " linestart")
1480 tail = text.index(last + "-1c lineend +1c")
1481 else:
1482 head = text.index("insert linestart")
1483 tail = text.index("insert lineend +1c")
1484 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001485 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001486 return head, tail, chars, lines
1487
1488 def set_region(self, head, tail, chars, lines):
1489 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001490 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001491 if newchars == chars:
1492 text.bell()
1493 return
1494 text.tag_remove("sel", "1.0", "end")
1495 text.mark_set("insert", head)
1496 text.undo_block_start()
1497 text.delete(head, tail)
1498 text.insert(head, newchars)
1499 text.undo_block_stop()
1500 text.tag_add("sel", head, "insert")
1501
1502 # Make string that displays as n leading blanks.
1503
1504 def _make_blanks(self, n):
1505 if self.usetabs:
1506 ntabs, nspaces = divmod(n, self.tabwidth)
1507 return '\t' * ntabs + ' ' * nspaces
1508 else:
1509 return ' ' * n
1510
1511 # Delete from beginning of line to insert point, then reinsert
1512 # column logical (meaning use tabs if appropriate) spaces.
1513
1514 def reindent_to(self, column):
1515 text = self.text
1516 text.undo_block_start()
1517 if text.compare("insert linestart", "!=", "insert"):
1518 text.delete("insert linestart", "insert")
1519 if column:
1520 text.insert("insert", self._make_blanks(column))
1521 text.undo_block_stop()
1522
1523 def _asktabwidth(self):
1524 return self.askinteger(
1525 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001526 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001527 parent=self.text,
1528 initialvalue=self.indentwidth,
1529 minvalue=2,
1530 maxvalue=16) or self.tabwidth
1531
1532 # Guess indentwidth from text content.
1533 # Return guessed indentwidth. This should not be believed unless
1534 # it's in a reasonable range (e.g., it will be 0 if no indented
1535 # blocks are found).
1536
1537 def guess_indent(self):
1538 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1539 if opener and indented:
1540 raw, indentsmall = classifyws(opener, self.tabwidth)
1541 raw, indentlarge = classifyws(indented, self.tabwidth)
1542 else:
1543 indentsmall = indentlarge = 0
1544 return indentlarge - indentsmall
1545
1546# "line.col" -> line, as an int
1547def index2line(index):
1548 return int(float(index))
1549
1550# Look at the leading whitespace in s.
1551# Return pair (# of leading ws characters,
1552# effective # of leading blanks after expanding
1553# tabs to width tabwidth)
1554
1555def classifyws(s, tabwidth):
1556 raw = effective = 0
1557 for ch in s:
1558 if ch == ' ':
1559 raw = raw + 1
1560 effective = effective + 1
1561 elif ch == '\t':
1562 raw = raw + 1
1563 effective = (effective // tabwidth + 1) * tabwidth
1564 else:
1565 break
1566 return raw, effective
1567
1568import tokenize
1569_tokenize = tokenize
1570del tokenize
1571
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001572class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001573
1574 # .run() chews over the Text widget, looking for a block opener
1575 # and the stmt following it. Returns a pair,
1576 # (line containing block opener, line containing stmt)
1577 # Either or both may be None.
1578
1579 def __init__(self, text, tabwidth):
1580 self.text = text
1581 self.tabwidth = tabwidth
1582 self.i = self.finished = 0
1583 self.blkopenline = self.indentedline = None
1584
1585 def readline(self):
1586 if self.finished:
1587 return ""
1588 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001589 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001590 if self.text.compare(mark, ">=", "end"):
1591 return ""
1592 return self.text.get(mark, mark + " lineend+1c")
1593
1594 def tokeneater(self, type, token, start, end, line,
1595 INDENT=_tokenize.INDENT,
1596 NAME=_tokenize.NAME,
1597 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1598 if self.finished:
1599 pass
1600 elif type == NAME and token in OPENERS:
1601 self.blkopenline = line
1602 elif type == INDENT and self.blkopenline:
1603 self.indentedline = line
1604 self.finished = 1
1605
1606 def run(self):
1607 save_tabsize = _tokenize.tabsize
1608 _tokenize.tabsize = self.tabwidth
1609 try:
1610 try:
1611 _tokenize.tokenize(self.readline, self.tokeneater)
1612 except _tokenize.TokenError:
1613 # since we cut off the tokenizer early, we can trigger
1614 # spurious errors
1615 pass
1616 finally:
1617 _tokenize.tabsize = save_tabsize
1618 return self.blkopenline, self.indentedline
1619
1620### end autoindent code ###
1621
David Scherer7aced172000-08-15 01:13:23 +00001622def prepstr(s):
1623 # Helper to extract the underscore from a string, e.g.
1624 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001625 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001626 if i >= 0:
1627 s = s[:i] + s[i+1:]
1628 return i, s
1629
1630
1631keynames = {
1632 'bracketleft': '[',
1633 'bracketright': ']',
1634 'slash': '/',
1635}
1636
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001637def get_accelerator(keydefs, eventname):
1638 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001639 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1640 # if not keylist:
1641 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1642 "<<open-module>>",
1643 "<<goto-line>>",
1644 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001645 return ""
1646 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001647 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001648 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1649 s = re.sub("Key-", "", s)
1650 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1651 s = re.sub("Control-", "Ctrl-", s)
1652 s = re.sub("-", "+", s)
1653 s = re.sub("><", " ", s)
1654 s = re.sub("<", "", s)
1655 s = re.sub(">", "", s)
1656 return s
1657
1658
1659def fixwordbreaks(root):
1660 # Make sure that Tk's double-click and next/previous word
1661 # operations use our definition of a word (i.e. an identifier)
1662 tk = root.tk
1663 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1664 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1665 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1666
1667
1668def test():
1669 root = Tk()
1670 fixwordbreaks(root)
1671 root.withdraw()
1672 if sys.argv[1:]:
1673 filename = sys.argv[1]
1674 else:
1675 filename = None
1676 edit = EditorWindow(root=root, filename=filename)
1677 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001678 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001679 root.mainloop()
1680 root.destroy()
1681
1682if __name__ == '__main__':
1683 test()