blob: fb05245dc312d62cf992b85c4538bf7a4b1f79dc [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):
473 self.text.tag_remove("sel", "1.0", "end")
474 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")
482 rmenu.tk_popup(event.x_root, event.y_root)
483 if iswin:
484 self.text.config(cursor="ibeam")
485
486 rmenu_specs = [
487 # ("Label", "<<virtual-event>>"), ...
488 ("Close", "<<close-window>>"), # Example
489 ]
490
491 def make_rmenu(self):
492 rmenu = Menu(self.text, tearoff=0)
493 for label, eventname in self.rmenu_specs:
494 def command(text=self.text, eventname=eventname):
495 text.event_generate(eventname)
496 rmenu.add_command(label=label, command=command)
497 self.rmenu = rmenu
498
499 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000500 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000501
Steven M. Gava3b55a892001-11-21 05:56:26 +0000502 def config_dialog(self, event=None):
503 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000504
David Scherer7aced172000-08-15 01:13:23 +0000505 def help_dialog(self, event=None):
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500506 if self.root:
507 parent = self.root
508 else:
509 parent = self.top
510 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000511
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000512 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000513 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000514 try:
515 os.startfile(self.help_url)
516 except WindowsError as why:
517 tkMessageBox.showerror(title='Document Start Failure',
518 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000519 else:
520 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000521 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000522
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000523 def cut(self,event):
524 self.text.event_generate("<<Cut>>")
525 return "break"
526
527 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000528 if not self.text.tag_ranges("sel"):
529 # There is no selection, so do nothing and maybe interrupt.
530 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000531 self.text.event_generate("<<Copy>>")
532 return "break"
533
534 def paste(self,event):
535 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000536 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000537 return "break"
538
David Scherer7aced172000-08-15 01:13:23 +0000539 def select_all(self, event=None):
540 self.text.tag_add("sel", "1.0", "end-1c")
541 self.text.mark_set("insert", "1.0")
542 self.text.see("insert")
543 return "break"
544
545 def remove_selection(self, event=None):
546 self.text.tag_remove("sel", "1.0", "end")
547 self.text.see("insert")
548
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000549 def move_at_edge_if_selection(self, edge_index):
550 """Cursor move begins at start or end of selection
551
552 When a left/right cursor key is pressed create and return to Tkinter a
553 function which causes a cursor move from the associated edge of the
554 selection.
555
556 """
557 self_text_index = self.text.index
558 self_text_mark_set = self.text.mark_set
559 edges_table = ("sel.first+1c", "sel.last-1c")
560 def move_at_edge(event):
561 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
562 try:
563 self_text_index("sel.first")
564 self_text_mark_set("insert", edges_table[edge_index])
565 except TclError:
566 pass
567 return move_at_edge
568
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000569 def del_word_left(self, event):
570 self.text.event_generate('<Meta-Delete>')
571 return "break"
572
573 def del_word_right(self, event):
574 self.text.event_generate('<Meta-d>')
575 return "break"
576
Steven M. Gavac5976402002-01-04 03:06:08 +0000577 def find_event(self, event):
578 SearchDialog.find(self.text)
579 return "break"
580
581 def find_again_event(self, event):
582 SearchDialog.find_again(self.text)
583 return "break"
584
585 def find_selection_event(self, event):
586 SearchDialog.find_selection(self.text)
587 return "break"
588
589 def find_in_files_event(self, event):
590 GrepDialog.grep(self.text, self.io, self.flist)
591 return "break"
592
593 def replace_event(self, event):
594 ReplaceDialog.replace(self.text)
595 return "break"
596
597 def goto_line_event(self, event):
598 text = self.text
599 lineno = tkSimpleDialog.askinteger("Goto",
600 "Go to line number:",parent=text)
601 if lineno is None:
602 return "break"
603 if lineno <= 0:
604 text.bell()
605 return "break"
606 text.mark_set("insert", "%d.0" % lineno)
607 text.see("insert")
608
David Scherer7aced172000-08-15 01:13:23 +0000609 def open_module(self, event=None):
610 # XXX Shouldn't this be in IOBinding or in FileList?
611 try:
612 name = self.text.get("sel.first", "sel.last")
613 except TclError:
614 name = ""
615 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000616 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000617 name = tkSimpleDialog.askstring("Module",
618 "Enter the name of a Python module\n"
619 "to search on sys.path and open:",
620 parent=self.text, initialvalue=name)
621 if name:
622 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000623 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000624 return
David Scherer7aced172000-08-15 01:13:23 +0000625 # XXX Ought to insert current file's directory in front of path
626 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000627 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000628 except (NameError, ImportError), msg:
629 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
630 return
631 if type != imp.PY_SOURCE:
632 tkMessageBox.showerror("Unsupported type",
633 "%s is not a source module" % name, parent=self.text)
634 return
635 if f:
636 f.close()
637 if self.flist:
638 self.flist.open(file)
639 else:
640 self.io.loadfile(file)
641
642 def open_class_browser(self, event=None):
643 filename = self.io.filename
644 if not filename:
645 tkMessageBox.showerror(
646 "No filename",
647 "This buffer has no associated filename",
648 master=self.text)
649 self.text.focus_set()
650 return None
651 head, tail = os.path.split(filename)
652 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000653 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000654 ClassBrowser.ClassBrowser(self.flist, base, [head])
655
656 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000657 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000658 PathBrowser.PathBrowser(self.flist)
659
660 def gotoline(self, lineno):
661 if lineno is not None and lineno > 0:
662 self.text.mark_set("insert", "%d.0" % lineno)
663 self.text.tag_remove("sel", "1.0", "end")
664 self.text.tag_add("sel", "insert", "insert +1l")
665 self.center()
666
667 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000668 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000669 return True
David Scherer7aced172000-08-15 01:13:23 +0000670 base, ext = os.path.splitext(os.path.basename(filename))
671 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000672 return True
David Scherer7aced172000-08-15 01:13:23 +0000673 try:
674 f = open(filename)
675 line = f.readline()
676 f.close()
677 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000678 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000679 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000680
681 def close_hook(self):
682 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000683 self.flist.unregister_maybe_terminate(self)
684 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000685
686 def set_close_hook(self, close_hook):
687 self.close_hook = close_hook
688
689 def filename_change_hook(self):
690 if self.flist:
691 self.flist.filename_changed_edit(self)
692 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000693 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000694 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000695
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000696 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000697 if self.color:
698 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000699 if self.ispythonsource(self.io.filename):
700 self.color = self.ColorDelegator()
701 # can add more colorizers here...
702 if self.color:
703 self.per.removefilter(self.undo)
704 self.per.insertfilter(self.color)
705 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000706
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000707 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000708 if not self.color:
709 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000710 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000711 self.per.removefilter(self.color)
712 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000713
Steven M. Gavab77d3432002-03-02 07:16:21 +0000714 def ResetColorizer(self):
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000715 "Update the colour theme"
716 # Called from self.filename_change_hook and from configDialog.py
717 self._rmcolorizer()
718 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000719 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000720 normal_colors = idleConf.GetHighlight(theme, 'normal')
721 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
722 select_colors = idleConf.GetHighlight(theme, 'hilite')
723 self.text.config(
724 foreground=normal_colors['foreground'],
725 background=normal_colors['background'],
726 insertbackground=cursor_color,
727 selectforeground=select_colors['foreground'],
728 selectbackground=select_colors['background'],
729 )
David Scherer7aced172000-08-15 01:13:23 +0000730
Steven M. Gavab1585412002-03-12 00:21:56 +0000731 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000732 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000733 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000734 fontWeight='normal'
735 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
736 fontWeight='bold'
737 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
738 idleConf.GetOption('main','EditorWindow','font-size'),
739 fontWeight))
740
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000741 def RemoveKeybindings(self):
742 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000743 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000744 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000745 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000746 self.text.event_delete(event, *keylist)
747 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000748 xkeydefs = idleConf.GetExtensionBindings(extensionName)
749 if xkeydefs:
750 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000751 self.text.event_delete(event, *keylist)
752
753 def ApplyKeybindings(self):
754 "Update the keybindings after they are changed"
755 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000756 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000757 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000758 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000759 xkeydefs = idleConf.GetExtensionBindings(extensionName)
760 if xkeydefs:
761 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000762 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000763 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000764 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000765 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000766 for item in menu[1]:
767 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000768 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000769 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000770 menu = self.menudict[menubarItem]
771 end = menu.index(END) + 1
772 for index in range(0, end):
773 if menu.type(index) == 'command':
774 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000775 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000776 itemName = menu.entrycget(index, 'label')
777 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000778 if menubarItem in menuEventDict:
779 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000780 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000781 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000782 accel = get_accelerator(keydefs, event)
783 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000784
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000785 def set_notabs_indentwidth(self):
786 "Update the indentwidth if changed and not using tabs in this window"
787 # Called from configDialog.py
788 if not self.usetabs:
789 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
790 type='int')
791
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000792 def reset_help_menu_entries(self):
793 "Update the additional help entries on the Help menu"
794 help_list = idleConf.GetAllExtraHelpSourcesList()
795 helpmenu = self.menudict['help']
796 # first delete the extra help entries, if any
797 helpmenu_length = helpmenu.index(END)
798 if helpmenu_length > self.base_helpmenu_length:
799 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
800 # then rebuild them
801 if help_list:
802 helpmenu.add_separator()
803 for entry in help_list:
804 cmd = self.__extra_help_callback(entry[1])
805 helpmenu.add_command(label=entry[0], command=cmd)
806 # and update the menu dictionary
807 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000808
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000809 def __extra_help_callback(self, helpfile):
810 "Create a callback with the helpfile value frozen at definition time"
811 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000812 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000813 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000814 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000815 try:
816 os.startfile(helpfile)
817 except WindowsError as why:
818 tkMessageBox.showerror(title='Document Start Failure',
819 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000820 else:
821 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000822 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000823
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000824 def update_recent_files_list(self, new_file=None):
825 "Load and update the recent files list and menus"
826 rf_list = []
827 if os.path.exists(self.recent_files_path):
828 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000829 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000830 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000831 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000832 rf_list_file.close()
833 if new_file:
834 new_file = os.path.abspath(new_file) + '\n'
835 if new_file in rf_list:
836 rf_list.remove(new_file) # move to top
837 rf_list.insert(0, new_file)
838 # clean and save the recent files list
839 bad_paths = []
840 for path in rf_list:
841 if '\0' in path or not os.path.exists(path[0:-1]):
842 bad_paths.append(path)
843 rf_list = [path for path in rf_list if path not in bad_paths]
844 ulchars = "1234567890ABCDEFGHIJK"
845 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000846 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800847 with open(self.recent_files_path, 'w') as rf_file:
848 rf_file.writelines(rf_list)
849 except IOError as err:
850 if not getattr(self.root, "recentfilelist_error_displayed", False):
851 self.root.recentfilelist_error_displayed = True
852 tkMessageBox.showerror(title='IDLE Error',
853 message='Unable to update Recent Files list:\n%s'
854 % str(err),
855 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000856 # for each edit window instance, construct the recent files menu
857 for instance in self.top.instance_dict.keys():
858 menu = instance.recent_files_menu
859 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000860 for i, file_name in enumerate(rf_list):
861 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000862 # make unicode string to display non-ASCII chars correctly
863 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000864 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000865 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000866 command=callback,
867 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000868
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000869 def __recent_file_callback(self, file_name):
870 def open_recent_file(fn_closure=file_name):
871 self.io.open(editFile=fn_closure)
872 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000873
David Scherer7aced172000-08-15 01:13:23 +0000874 def saved_change_hook(self):
875 short = self.short_title()
876 long = self.long_title()
877 if short and long:
878 title = short + " - " + long
879 elif short:
880 title = short
881 elif long:
882 title = long
883 else:
884 title = "Untitled"
885 icon = short or long or title
886 if not self.get_saved():
887 title = "*%s*" % title
888 icon = "*%s" % icon
889 self.top.wm_title(title)
890 self.top.wm_iconname(icon)
891
892 def get_saved(self):
893 return self.undo.get_saved()
894
895 def set_saved(self, flag):
896 self.undo.set_saved(flag)
897
898 def reset_undo(self):
899 self.undo.reset_undo()
900
901 def short_title(self):
902 filename = self.io.filename
903 if filename:
904 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000905 # return unicode string to display non-ASCII chars correctly
906 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000907
908 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000909 # return unicode string to display non-ASCII chars correctly
910 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000911
912 def center_insert_event(self, event):
913 self.center()
914
915 def center(self, mark="insert"):
916 text = self.text
917 top, bot = self.getwindowlines()
918 lineno = self.getlineno(mark)
919 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000920 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000921 text.yview(float(newtop))
922
923 def getwindowlines(self):
924 text = self.text
925 top = self.getlineno("@0,0")
926 bot = self.getlineno("@0,65535")
927 if top == bot and text.winfo_height() == 1:
928 # Geometry manager hasn't run yet
929 height = int(text['height'])
930 bot = top + height - 1
931 return top, bot
932
933 def getlineno(self, mark="insert"):
934 text = self.text
935 return int(float(text.index(mark)))
936
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000937 def get_geometry(self):
938 "Return (width, height, x, y)"
939 geom = self.top.wm_geometry()
940 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
941 tuple = (map(int, m.groups()))
942 return tuple
943
David Scherer7aced172000-08-15 01:13:23 +0000944 def close_event(self, event):
945 self.close()
946
947 def maybesave(self):
948 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000949 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000950 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000951 self.top.deiconify()
952 self.top.lower()
953 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000954 return self.io.maybesave()
955
956 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000957 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +0000958 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000959 self._close()
960 return reply
961
962 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000963 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000964 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000965 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000966 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000967 self.io.close()
968 self.io = None
969 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000970 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000971 self.color.close(False)
972 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000973 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000974 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000975 self.per.close()
976 self.per = None
977 self.top.destroy()
978 if self.close_hook:
979 # unless override: unregister from flist, terminate if last window
980 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000981
982 def load_extensions(self):
983 self.extensions = {}
984 self.load_standard_extensions()
985
986 def unload_extensions(self):
987 for ins in self.extensions.values():
988 if hasattr(ins, "close"):
989 ins.close()
990 self.extensions = {}
991
992 def load_standard_extensions(self):
993 for name in self.get_standard_extension_names():
994 try:
995 self.load_extension(name)
996 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000997 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +0000998 import traceback
999 traceback.print_exc()
1000
1001 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001002 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001003
1004 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001005 try:
1006 mod = __import__(name, globals(), locals(), [])
1007 except ImportError:
1008 print "\nFailed to import extension: ", name
1009 return
David Scherer7aced172000-08-15 01:13:23 +00001010 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001011 keydefs = idleConf.GetExtensionBindings(name)
1012 if hasattr(cls, "menudefs"):
1013 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001014 ins = cls(self)
1015 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001016 if keydefs:
1017 self.apply_bindings(keydefs)
1018 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001019 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001020 while methodname[:1] == '<':
1021 methodname = methodname[1:]
1022 while methodname[-1:] == '>':
1023 methodname = methodname[:-1]
1024 methodname = methodname + "_event"
1025 if hasattr(ins, methodname):
1026 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001027
1028 def apply_bindings(self, keydefs=None):
1029 if keydefs is None:
1030 keydefs = self.Bindings.default_keydefs
1031 text = self.text
1032 text.keydefs = keydefs
1033 for event, keylist in keydefs.items():
1034 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001035 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001036
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001037 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001038 """Add appropriate entries to the menus and submenus
1039
1040 Menus that are absent or None in self.menudict are ignored.
1041 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001042 if menudefs is None:
1043 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001044 if keydefs is None:
1045 keydefs = self.Bindings.default_keydefs
1046 menudict = self.menudict
1047 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001048 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001049 menu = menudict.get(mname)
1050 if not menu:
1051 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001052 for entry in entrylist:
1053 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001054 menu.add_separator()
1055 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001056 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001057 checkbutton = (label[:1] == '!')
1058 if checkbutton:
1059 label = label[1:]
1060 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001061 accelerator = get_accelerator(keydefs, eventname)
1062 def command(text=text, eventname=eventname):
1063 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001064 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001065 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001066 menu.add_checkbutton(label=label, underline=underline,
1067 command=command, accelerator=accelerator,
1068 variable=var)
1069 else:
1070 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001071 command=command,
1072 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001073
1074 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001075 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001076 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001077 value = var.get()
1078 return value
1079 else:
1080 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001081
1082 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001083 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001084 if var:
1085 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001086 else:
1087 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001088
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001089 def get_var_obj(self, name, vartype=None):
1090 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001091 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001092 # create a Tkinter variable object with self.text as master:
1093 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001094 return var
1095
1096 # Tk implementations of "virtual text methods" -- each platform
1097 # reusing IDLE's support code needs to define these for its GUI's
1098 # flavor of widget.
1099
1100 # Is character at text_index in a Python string? Return 0 for
1101 # "guaranteed no", true for anything else. This info is expensive
1102 # to compute ab initio, but is probably already known by the
1103 # platform's colorizer.
1104
1105 def is_char_in_string(self, text_index):
1106 if self.color:
1107 # Return true iff colorizer hasn't (re)gotten this far
1108 # yet, or the character is tagged as being in a string
1109 return self.text.tag_prevrange("TODO", text_index) or \
1110 "STRING" in self.text.tag_names(text_index)
1111 else:
1112 # The colorizer is missing: assume the worst
1113 return 1
1114
1115 # If a selection is defined in the text widget, return (start,
1116 # end) as Tkinter text indices, otherwise return (None, None)
1117 def get_selection_indices(self):
1118 try:
1119 first = self.text.index("sel.first")
1120 last = self.text.index("sel.last")
1121 return first, last
1122 except TclError:
1123 return None, None
1124
1125 # Return the text widget's current view of what a tab stop means
1126 # (equivalent width in spaces).
1127
1128 def get_tabwidth(self):
1129 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1130 return int(current)
1131
1132 # Set the text widget's current view of what a tab stop means.
1133
1134 def set_tabwidth(self, newtabwidth):
1135 text = self.text
1136 if self.get_tabwidth() != newtabwidth:
1137 pixels = text.tk.call("font", "measure", text["font"],
1138 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001139 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001140 text.configure(tabs=pixels)
1141
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001142 # If ispythonsource and guess are true, guess a good value for
1143 # indentwidth based on file content (if possible), and if
1144 # indentwidth != tabwidth set usetabs false.
1145 # In any case, adjust the Text widget's view of what a tab
1146 # character means.
1147
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001148 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001149 if guess and ispythonsource:
1150 i = self.guess_indent()
1151 if 2 <= i <= 8:
1152 self.indentwidth = i
1153 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001154 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001155 self.set_tabwidth(self.tabwidth)
1156
1157 def smart_backspace_event(self, event):
1158 text = self.text
1159 first, last = self.get_selection_indices()
1160 if first and last:
1161 text.delete(first, last)
1162 text.mark_set("insert", first)
1163 return "break"
1164 # Delete whitespace left, until hitting a real char or closest
1165 # preceding virtual tab stop.
1166 chars = text.get("insert linestart", "insert")
1167 if chars == '':
1168 if text.compare("insert", ">", "1.0"):
1169 # easy: delete preceding newline
1170 text.delete("insert-1c")
1171 else:
1172 text.bell() # at start of buffer
1173 return "break"
1174 if chars[-1] not in " \t":
1175 # easy: delete preceding real char
1176 text.delete("insert-1c")
1177 return "break"
1178 # Ick. It may require *inserting* spaces if we back up over a
1179 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001180 tabwidth = self.tabwidth
1181 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001182 assert have > 0
1183 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001184 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001185 if self.context_use_ps1:
1186 last_line_of_prompt = sys.ps1.split('\n')[-1]
1187 else:
1188 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001189 ncharsdeleted = 0
1190 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001191 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001192 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001193 chars = chars[:-1]
1194 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001195 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001196 if have <= want or chars[-1] not in " \t":
1197 break
1198 text.undo_block_start()
1199 text.delete("insert-%dc" % ncharsdeleted, "insert")
1200 if have < want:
1201 text.insert("insert", ' ' * (want - have))
1202 text.undo_block_stop()
1203 return "break"
1204
1205 def smart_indent_event(self, event):
1206 # if intraline selection:
1207 # delete it
1208 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001209 # do indent-region
1210 # else:
1211 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001212 text = self.text
1213 first, last = self.get_selection_indices()
1214 text.undo_block_start()
1215 try:
1216 if first and last:
1217 if index2line(first) != index2line(last):
1218 return self.indent_region_event(event)
1219 text.delete(first, last)
1220 text.mark_set("insert", first)
1221 prefix = text.get("insert linestart", "insert")
1222 raw, effective = classifyws(prefix, self.tabwidth)
1223 if raw == len(prefix):
1224 # only whitespace to the left
1225 self.reindent_to(effective + self.indentwidth)
1226 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001227 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001228 if self.usetabs:
1229 pad = '\t'
1230 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001231 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001232 n = self.indentwidth
1233 pad = ' ' * (n - effective % n)
1234 text.insert("insert", pad)
1235 text.see("insert")
1236 return "break"
1237 finally:
1238 text.undo_block_stop()
1239
1240 def newline_and_indent_event(self, event):
1241 text = self.text
1242 first, last = self.get_selection_indices()
1243 text.undo_block_start()
1244 try:
1245 if first and last:
1246 text.delete(first, last)
1247 text.mark_set("insert", first)
1248 line = text.get("insert linestart", "insert")
1249 i, n = 0, len(line)
1250 while i < n and line[i] in " \t":
1251 i = i+1
1252 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001253 # the cursor is in or at leading indentation in a continuation
1254 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001255 text.insert("insert linestart", '\n')
1256 return "break"
1257 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001258 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001259 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001260 last_line_of_prompt = sys.ps1.split('\n')[-1]
1261 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001262 line = line[:-1]
1263 i = i+1
1264 if i:
1265 text.delete("insert - %d chars" % i, "insert")
1266 # strip whitespace after insert point
1267 while text.get("insert") in " \t":
1268 text.delete("insert")
1269 # start new line
1270 text.insert("insert", '\n')
1271
1272 # adjust indentation for continuations and block
1273 # open/close first need to find the last stmt
1274 lno = index2line(text.index('insert'))
1275 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001276 if not self.context_use_ps1:
1277 for context in self.num_context_lines:
1278 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001279 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001280 rawtext = text.get(startatindex, "insert")
1281 y.set_str(rawtext)
1282 bod = y.find_good_parse_start(
1283 self.context_use_ps1,
1284 self._build_char_in_string_func(startatindex))
1285 if bod is not None or startat == 1:
1286 break
1287 y.set_lo(bod or 0)
1288 else:
1289 r = text.tag_prevrange("console", "insert")
1290 if r:
1291 startatindex = r[1]
1292 else:
1293 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001294 rawtext = text.get(startatindex, "insert")
1295 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001296 y.set_lo(0)
1297
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001298 c = y.get_continuation_type()
1299 if c != PyParse.C_NONE:
1300 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001301 if c == PyParse.C_STRING_FIRST_LINE:
1302 # after the first line of a string; do not indent at all
1303 pass
1304 elif c == PyParse.C_STRING_NEXT_LINES:
1305 # inside a string which started before this line;
1306 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001307 text.insert("insert", indent)
1308 elif c == PyParse.C_BRACKET:
1309 # line up with the first (if any) element of the
1310 # last open bracket structure; else indent one
1311 # level beyond the indent of the line with the
1312 # last open bracket
1313 self.reindent_to(y.compute_bracket_indent())
1314 elif c == PyParse.C_BACKSLASH:
1315 # if more than one line in this stmt already, just
1316 # mimic the current indent; else if initial line
1317 # has a start on an assignment stmt, indent to
1318 # beyond leftmost =; else to beyond first chunk of
1319 # non-whitespace on initial line
1320 if y.get_num_lines_in_stmt() > 1:
1321 text.insert("insert", indent)
1322 else:
1323 self.reindent_to(y.compute_backslash_indent())
1324 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001325 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001326 return "break"
1327
1328 # This line starts a brand new stmt; indent relative to
1329 # indentation of initial line of closest preceding
1330 # interesting stmt.
1331 indent = y.get_base_indent_string()
1332 text.insert("insert", indent)
1333 if y.is_block_opener():
1334 self.smart_indent_event(event)
1335 elif indent and y.is_block_closer():
1336 self.smart_backspace_event(event)
1337 return "break"
1338 finally:
1339 text.see("insert")
1340 text.undo_block_stop()
1341
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001342 # Our editwin provides a is_char_in_string function that works
1343 # with a Tk text index, but PyParse only knows about offsets into
1344 # a string. This builds a function for PyParse that accepts an
1345 # offset.
1346
1347 def _build_char_in_string_func(self, startindex):
1348 def inner(offset, _startindex=startindex,
1349 _icis=self.is_char_in_string):
1350 return _icis(_startindex + "+%dc" % offset)
1351 return inner
1352
1353 def indent_region_event(self, event):
1354 head, tail, chars, lines = self.get_region()
1355 for pos in range(len(lines)):
1356 line = lines[pos]
1357 if line:
1358 raw, effective = classifyws(line, self.tabwidth)
1359 effective = effective + self.indentwidth
1360 lines[pos] = self._make_blanks(effective) + line[raw:]
1361 self.set_region(head, tail, chars, lines)
1362 return "break"
1363
1364 def dedent_region_event(self, event):
1365 head, tail, chars, lines = self.get_region()
1366 for pos in range(len(lines)):
1367 line = lines[pos]
1368 if line:
1369 raw, effective = classifyws(line, self.tabwidth)
1370 effective = max(effective - self.indentwidth, 0)
1371 lines[pos] = self._make_blanks(effective) + line[raw:]
1372 self.set_region(head, tail, chars, lines)
1373 return "break"
1374
1375 def comment_region_event(self, event):
1376 head, tail, chars, lines = self.get_region()
1377 for pos in range(len(lines) - 1):
1378 line = lines[pos]
1379 lines[pos] = '##' + line
1380 self.set_region(head, tail, chars, lines)
1381
1382 def uncomment_region_event(self, event):
1383 head, tail, chars, lines = self.get_region()
1384 for pos in range(len(lines)):
1385 line = lines[pos]
1386 if not line:
1387 continue
1388 if line[:2] == '##':
1389 line = line[2:]
1390 elif line[:1] == '#':
1391 line = line[1:]
1392 lines[pos] = line
1393 self.set_region(head, tail, chars, lines)
1394
1395 def tabify_region_event(self, event):
1396 head, tail, chars, lines = self.get_region()
1397 tabwidth = self._asktabwidth()
1398 for pos in range(len(lines)):
1399 line = lines[pos]
1400 if line:
1401 raw, effective = classifyws(line, tabwidth)
1402 ntabs, nspaces = divmod(effective, tabwidth)
1403 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1404 self.set_region(head, tail, chars, lines)
1405
1406 def untabify_region_event(self, event):
1407 head, tail, chars, lines = self.get_region()
1408 tabwidth = self._asktabwidth()
1409 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001410 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001411 self.set_region(head, tail, chars, lines)
1412
1413 def toggle_tabs_event(self, event):
1414 if self.askyesno(
1415 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001416 "Turn tabs " + ("on", "off")[self.usetabs] +
1417 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001418 ("will be", "remains at")[self.usetabs] + " 8." +
1419 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001420 parent=self.text):
1421 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001422 # Try to prevent inconsistent indentation.
1423 # User must change indent width manually after using tabs.
1424 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001425 return "break"
1426
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001427 # XXX this isn't bound to anything -- see tabwidth comments
1428## def change_tabwidth_event(self, event):
1429## new = self._asktabwidth()
1430## if new != self.tabwidth:
1431## self.tabwidth = new
1432## self.set_indentation_params(0, guess=0)
1433## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001434
1435 def change_indentwidth_event(self, event):
1436 new = self.askinteger(
1437 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001438 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001439 parent=self.text,
1440 initialvalue=self.indentwidth,
1441 minvalue=2,
1442 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001443 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001444 self.indentwidth = new
1445 return "break"
1446
1447 def get_region(self):
1448 text = self.text
1449 first, last = self.get_selection_indices()
1450 if first and last:
1451 head = text.index(first + " linestart")
1452 tail = text.index(last + "-1c lineend +1c")
1453 else:
1454 head = text.index("insert linestart")
1455 tail = text.index("insert lineend +1c")
1456 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001457 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001458 return head, tail, chars, lines
1459
1460 def set_region(self, head, tail, chars, lines):
1461 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001462 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001463 if newchars == chars:
1464 text.bell()
1465 return
1466 text.tag_remove("sel", "1.0", "end")
1467 text.mark_set("insert", head)
1468 text.undo_block_start()
1469 text.delete(head, tail)
1470 text.insert(head, newchars)
1471 text.undo_block_stop()
1472 text.tag_add("sel", head, "insert")
1473
1474 # Make string that displays as n leading blanks.
1475
1476 def _make_blanks(self, n):
1477 if self.usetabs:
1478 ntabs, nspaces = divmod(n, self.tabwidth)
1479 return '\t' * ntabs + ' ' * nspaces
1480 else:
1481 return ' ' * n
1482
1483 # Delete from beginning of line to insert point, then reinsert
1484 # column logical (meaning use tabs if appropriate) spaces.
1485
1486 def reindent_to(self, column):
1487 text = self.text
1488 text.undo_block_start()
1489 if text.compare("insert linestart", "!=", "insert"):
1490 text.delete("insert linestart", "insert")
1491 if column:
1492 text.insert("insert", self._make_blanks(column))
1493 text.undo_block_stop()
1494
1495 def _asktabwidth(self):
1496 return self.askinteger(
1497 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001498 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001499 parent=self.text,
1500 initialvalue=self.indentwidth,
1501 minvalue=2,
1502 maxvalue=16) or self.tabwidth
1503
1504 # Guess indentwidth from text content.
1505 # Return guessed indentwidth. This should not be believed unless
1506 # it's in a reasonable range (e.g., it will be 0 if no indented
1507 # blocks are found).
1508
1509 def guess_indent(self):
1510 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1511 if opener and indented:
1512 raw, indentsmall = classifyws(opener, self.tabwidth)
1513 raw, indentlarge = classifyws(indented, self.tabwidth)
1514 else:
1515 indentsmall = indentlarge = 0
1516 return indentlarge - indentsmall
1517
1518# "line.col" -> line, as an int
1519def index2line(index):
1520 return int(float(index))
1521
1522# Look at the leading whitespace in s.
1523# Return pair (# of leading ws characters,
1524# effective # of leading blanks after expanding
1525# tabs to width tabwidth)
1526
1527def classifyws(s, tabwidth):
1528 raw = effective = 0
1529 for ch in s:
1530 if ch == ' ':
1531 raw = raw + 1
1532 effective = effective + 1
1533 elif ch == '\t':
1534 raw = raw + 1
1535 effective = (effective // tabwidth + 1) * tabwidth
1536 else:
1537 break
1538 return raw, effective
1539
1540import tokenize
1541_tokenize = tokenize
1542del tokenize
1543
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001544class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001545
1546 # .run() chews over the Text widget, looking for a block opener
1547 # and the stmt following it. Returns a pair,
1548 # (line containing block opener, line containing stmt)
1549 # Either or both may be None.
1550
1551 def __init__(self, text, tabwidth):
1552 self.text = text
1553 self.tabwidth = tabwidth
1554 self.i = self.finished = 0
1555 self.blkopenline = self.indentedline = None
1556
1557 def readline(self):
1558 if self.finished:
1559 return ""
1560 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001561 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001562 if self.text.compare(mark, ">=", "end"):
1563 return ""
1564 return self.text.get(mark, mark + " lineend+1c")
1565
1566 def tokeneater(self, type, token, start, end, line,
1567 INDENT=_tokenize.INDENT,
1568 NAME=_tokenize.NAME,
1569 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1570 if self.finished:
1571 pass
1572 elif type == NAME and token in OPENERS:
1573 self.blkopenline = line
1574 elif type == INDENT and self.blkopenline:
1575 self.indentedline = line
1576 self.finished = 1
1577
1578 def run(self):
1579 save_tabsize = _tokenize.tabsize
1580 _tokenize.tabsize = self.tabwidth
1581 try:
1582 try:
1583 _tokenize.tokenize(self.readline, self.tokeneater)
1584 except _tokenize.TokenError:
1585 # since we cut off the tokenizer early, we can trigger
1586 # spurious errors
1587 pass
1588 finally:
1589 _tokenize.tabsize = save_tabsize
1590 return self.blkopenline, self.indentedline
1591
1592### end autoindent code ###
1593
David Scherer7aced172000-08-15 01:13:23 +00001594def prepstr(s):
1595 # Helper to extract the underscore from a string, e.g.
1596 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001597 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001598 if i >= 0:
1599 s = s[:i] + s[i+1:]
1600 return i, s
1601
1602
1603keynames = {
1604 'bracketleft': '[',
1605 'bracketright': ']',
1606 'slash': '/',
1607}
1608
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001609def get_accelerator(keydefs, eventname):
1610 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001611 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1612 # if not keylist:
1613 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1614 "<<open-module>>",
1615 "<<goto-line>>",
1616 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001617 return ""
1618 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001619 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001620 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1621 s = re.sub("Key-", "", s)
1622 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1623 s = re.sub("Control-", "Ctrl-", s)
1624 s = re.sub("-", "+", s)
1625 s = re.sub("><", " ", s)
1626 s = re.sub("<", "", s)
1627 s = re.sub(">", "", s)
1628 return s
1629
1630
1631def fixwordbreaks(root):
1632 # Make sure that Tk's double-click and next/previous word
1633 # operations use our definition of a word (i.e. an identifier)
1634 tk = root.tk
1635 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1636 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1637 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1638
1639
1640def test():
1641 root = Tk()
1642 fixwordbreaks(root)
1643 root.withdraw()
1644 if sys.argv[1:]:
1645 filename = sys.argv[1]
1646 else:
1647 filename = None
1648 edit = EditorWindow(root=root, filename=filename)
1649 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001650 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001651 root.mainloop()
1652 root.destroy()
1653
1654if __name__ == '__main__':
1655 test()