blob: 1230047ed2b16d211d5c9fd9fa7da56c0c53a199 [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
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000068class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +000069 from idlelib.Percolator import Percolator
70 from idlelib.ColorDelegator import ColorDelegator
71 from idlelib.UndoDelegator import UndoDelegator
72 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
73 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +000074 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +000075 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000076
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000077 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000078
79 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000080 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000081 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000082 if sys.platform.count('linux'):
83 # look for html docs in a couple of standard places
84 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
85 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
86 dochome = '/var/www/html/python/index.html'
87 else:
88 basepath = '/usr/share/doc/' # standard location
89 dochome = os.path.join(basepath, pyver,
90 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000091 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000092 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000093 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000094 if os.path.isfile(chmfile):
95 dochome = chmfile
Ronald Oussoren19302d92006-06-11 14:33:36 +000096 elif macosxSupport.runningAsOSXApp():
97 # documentation is stored inside the python framework
98 dochome = os.path.join(sys.prefix,
99 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000100 dochome = os.path.normpath(dochome)
101 if os.path.isfile(dochome):
102 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +0000103 if sys.platform == 'darwin':
104 # Safari requires real file:-URLs
105 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000106 else:
Raymond Hettinger7b4c2be2009-01-20 10:46:23 +0000107 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000108 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000109 self.flist = flist
110 root = root or flist.root
111 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +0000112 try:
113 sys.ps1
114 except AttributeError:
115 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000116 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000117 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000118 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000119 self.tkinter_vars = flist.vars
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200120 #self.top.instance_dict makes flist.inversedict available to
121 #configDialog.py so it can access all EditorWindow instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000122 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000123 else:
124 self.tkinter_vars = {} # keys: Tkinter event names
125 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000126 self.top.instance_dict = {}
127 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000128 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000129 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000130 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000131 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000132 text_options = {
133 'name': 'text',
134 'padx': 5,
135 'wrap': 'none',
136 'width': self.width,
137 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
138 if TkVersion >= 8.5:
139 # Starting with tk 8.5 we have to set the new tabstyle option
140 # to 'wordprocessor' to achieve the same display of tabs as in
141 # older tk versions.
142 text_options['tabstyle'] = 'wordprocessor'
143 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000144 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000145
146 self.createmenubar()
147 self.apply_bindings()
148
149 self.top.protocol("WM_DELETE_WINDOW", self.close)
150 self.top.bind("<<close-window>>", self.close_event)
Ronald Oussoren17db4952006-07-23 09:41:09 +0000151 if macosxSupport.runningAsOSXApp():
152 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000153 text.bind('<<close-window>>', self.close_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000154 # Some OS X systems have only one mouse button,
155 # so use control-click for pulldown menus there.
156 # (Note, AquaTk defines <2> as the right button if
157 # present and the Tk Text widget already binds <2>.)
158 text.bind("<Control-Button-1>",self.right_menu_event)
159 else:
160 # Elsewhere, use right-click for pulldown menus.
161 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000162 text.bind("<<cut>>", self.cut)
163 text.bind("<<copy>>", self.copy)
164 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000165 text.bind("<<center-insert>>", self.center_insert_event)
166 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000167 text.bind("<<python-docs>>", self.python_docs)
168 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000169 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000170 text.bind("<<open-module>>", self.open_module)
171 text.bind("<<do-nothing>>", lambda event: "break")
172 text.bind("<<select-all>>", self.select_all)
173 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000174 text.bind("<<find>>", self.find_event)
175 text.bind("<<find-again>>", self.find_again_event)
176 text.bind("<<find-in-files>>", self.find_in_files_event)
177 text.bind("<<find-selection>>", self.find_selection_event)
178 text.bind("<<replace>>", self.replace_event)
179 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000180 text.bind("<<smart-backspace>>",self.smart_backspace_event)
181 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
182 text.bind("<<smart-indent>>",self.smart_indent_event)
183 text.bind("<<indent-region>>",self.indent_region_event)
184 text.bind("<<dedent-region>>",self.dedent_region_event)
185 text.bind("<<comment-region>>",self.comment_region_event)
186 text.bind("<<uncomment-region>>",self.uncomment_region_event)
187 text.bind("<<tabify-region>>",self.tabify_region_event)
188 text.bind("<<untabify-region>>",self.untabify_region_event)
189 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
190 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000191 text.bind("<Left>", self.move_at_edge_if_selection(0))
192 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000193 text.bind("<<del-word-left>>", self.del_word_left)
194 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000195 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000196
David Scherer7aced172000-08-15 01:13:23 +0000197 if flist:
198 flist.inversedict[self] = key
199 if key:
200 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000201 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000202 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
203 text.bind("<<open-class-browser>>", self.open_class_browser)
204 text.bind("<<open-path-browser>>", self.open_path_browser)
205
Steven M. Gava898a3652001-10-07 11:10:44 +0000206 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000207 vbar['command'] = text.yview
208 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000209 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000210 fontWeight = 'normal'
211 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000212 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000213 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
214 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
215 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000216 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
217 text.pack(side=TOP, fill=BOTH, expand=1)
218 text.focus_set()
219
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000220 # usetabs true -> literal tab characters are used by indent and
221 # dedent cmds, possibly mixed with spaces if
222 # indentwidth is not a multiple of tabwidth,
223 # which will cause Tabnanny to nag!
224 # false -> tab characters are converted to spaces by indent
225 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000226 # Although use-spaces=0 can be configured manually in config-main.def,
227 # configuration of tabs v. spaces is not supported in the configuration
228 # dialog. IDLE promotes the preferred Python indentation: use spaces!
229 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
230 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000231
232 # tabwidth is the display width of a literal tab character.
233 # CAUTION: telling Tk to use anything other than its default
234 # tab setting causes it to use an entirely different tabbing algorithm,
235 # treating tab stops as fixed distances from the left margin.
236 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000237 self.tabwidth = 8 # must remain 8 until Tk is fixed.
238
239 # indentwidth is the number of screen characters per indent level.
240 # The recommended Python indentation is four spaces.
241 self.indentwidth = self.tabwidth
242 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000243
244 # If context_use_ps1 is true, parsing searches back for a ps1 line;
245 # else searches for a popular (if, def, ...) Python stmt.
246 self.context_use_ps1 = False
247
248 # When searching backwards for a reliable place to begin parsing,
249 # first start num_context_lines[0] lines back, then
250 # num_context_lines[1] lines back if that didn't work, and so on.
251 # The last value should be huge (larger than the # of lines in a
252 # conceivable file).
253 # Making the initial values larger slows things down more often.
254 self.num_context_lines = 50, 500, 5000000
255
David Scherer7aced172000-08-15 01:13:23 +0000256 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000257
258 self.undo = undo = self.UndoDelegator()
259 per.insertfilter(undo)
260 text.undo_block_start = undo.undo_block_start
261 text.undo_block_stop = undo.undo_block_stop
262 undo.set_saved_change_hook(self.saved_change_hook)
263
264 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000265 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000266 io.set_filename_change_hook(self.filename_change_hook)
267
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000268 # Create the recent files submenu
269 self.recent_files_menu = Menu(self.menubar)
270 self.menudict['file'].insert_cascade(3, label='Recent Files',
271 underline=0,
272 menu=self.recent_files_menu)
273 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000274
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000275 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000276 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000277 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000278 io.loadfile(filename)
279 else:
280 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000281 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000282 self.saved_change_hook()
283
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000284 self.set_indentation_params(self.ispythonsource(filename))
285
David Scherer7aced172000-08-15 01:13:23 +0000286 self.load_extensions()
287
288 menu = self.menudict.get('windows')
289 if menu:
290 end = menu.index("end")
291 if end is None:
292 end = -1
293 if end >= 0:
294 menu.add_separator()
295 end = end + 1
296 self.wmenu_end = end
297 WindowList.register_callback(self.postwindowsmenu)
298
299 # Some abstractions so IDLE extensions are cross-IDE
300 self.askyesno = tkMessageBox.askyesno
301 self.askinteger = tkSimpleDialog.askinteger
302 self.showerror = tkMessageBox.showerror
303
Martin v. Löwis307021f2005-11-27 16:59:04 +0000304 def _filename_to_unicode(self, filename):
305 """convert filename to unicode in order to display it in Tk"""
306 if isinstance(filename, unicode) or not filename:
307 return filename
308 else:
309 try:
310 return filename.decode(self.filesystemencoding)
311 except UnicodeDecodeError:
312 # XXX
313 try:
314 return filename.decode(self.encoding)
315 except UnicodeDecodeError:
316 # byte-to-byte conversion
317 return filename.decode('iso8859-1')
318
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000319 def new_callback(self, event):
320 dirname, basename = self.io.defaultfilename()
321 self.flist.new(dirname)
322 return "break"
323
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000324 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400325 if (event.state & 4) != 0 and event.keysym == "Home":
326 # state&4==Control. If <Control-Home>, use the Tk binding.
327 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000328 if self.text.index("iomark") and \
329 self.text.compare("iomark", "<=", "insert lineend") and \
330 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400331 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000332 insertpt = int(self.text.index("iomark").split(".")[1])
333 else:
334 line = self.text.get("insert linestart", "insert lineend")
335 for insertpt in xrange(len(line)):
336 if line[insertpt] not in (' ','\t'):
337 break
338 else:
339 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000340 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000341 if insertpt == lineat:
342 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000343 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000344 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400345 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000346 self.text.tag_remove("sel", "1.0", "end")
347 else:
348 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400349 self.text.mark_set("my_anchor", "insert") # there was no previous selection
350 else:
351 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
352 self.text.mark_set("my_anchor", "sel.first") # extend back
353 else:
354 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000355 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400356 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000357 if self.text.compare(first,">",last):
358 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000359 self.text.tag_remove("sel", "1.0", "end")
360 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000361 self.text.mark_set("insert", dest)
362 self.text.see("insert")
363 return "break"
364
David Scherer7aced172000-08-15 01:13:23 +0000365 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000366 self.status_bar = self.MultiStatusBar(self.top)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000367 if macosxSupport.runningAsOSXApp():
368 # Insert some padding to avoid obscuring some of the statusbar
369 # by the resize widget.
370 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000371 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
372 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
373 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000374 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
375 self.text.event_add("<<set-line-and-column>>",
376 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000377 self.text.after_idle(self.set_line_and_column)
378
379 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000380 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000381 self.status_bar.set_label('column', 'Col: %s' % column)
382 self.status_bar.set_label('line', 'Ln: %s' % line)
383
David Scherer7aced172000-08-15 01:13:23 +0000384 menu_specs = [
385 ("file", "_File"),
386 ("edit", "_Edit"),
387 ("format", "F_ormat"),
388 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000389 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000390 ("windows", "_Windows"),
391 ("help", "_Help"),
392 ]
393
Ronald Oussoren19302d92006-06-11 14:33:36 +0000394 if macosxSupport.runningAsOSXApp():
395 del menu_specs[-3]
396 menu_specs[-2] = ("windows", "_Window")
397
398
David Scherer7aced172000-08-15 01:13:23 +0000399 def createmenubar(self):
400 mbar = self.menubar
401 self.menudict = menudict = {}
402 for name, label in self.menu_specs:
403 underline, label = prepstr(label)
404 menudict[name] = menu = Menu(mbar, name=name)
405 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000406
Ned Deily4a705502011-01-18 04:33:22 +0000407 if macosxSupport.isCarbonAquaTk(self.root):
Ronald Oussoren19302d92006-06-11 14:33:36 +0000408 # Insert the application menu
409 menudict['application'] = menu = Menu(mbar, name='apple')
410 mbar.add_cascade(label='IDLE', menu=menu)
411
David Scherer7aced172000-08-15 01:13:23 +0000412 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000413 self.base_helpmenu_length = self.menudict['help'].index(END)
414 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000415
416 def postwindowsmenu(self):
417 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000418 menu = self.menudict['windows']
419 end = menu.index("end")
420 if end is None:
421 end = -1
422 if end > self.wmenu_end:
423 menu.delete(self.wmenu_end+1, end)
424 WindowList.add_windows_to_menu(menu)
425
426 rmenu = None
427
428 def right_menu_event(self, event):
429 self.text.tag_remove("sel", "1.0", "end")
430 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
431 if not self.rmenu:
432 self.make_rmenu()
433 rmenu = self.rmenu
434 self.event = event
435 iswin = sys.platform[:3] == 'win'
436 if iswin:
437 self.text.config(cursor="arrow")
438 rmenu.tk_popup(event.x_root, event.y_root)
439 if iswin:
440 self.text.config(cursor="ibeam")
441
442 rmenu_specs = [
443 # ("Label", "<<virtual-event>>"), ...
444 ("Close", "<<close-window>>"), # Example
445 ]
446
447 def make_rmenu(self):
448 rmenu = Menu(self.text, tearoff=0)
449 for label, eventname in self.rmenu_specs:
450 def command(text=self.text, eventname=eventname):
451 text.event_generate(eventname)
452 rmenu.add_command(label=label, command=command)
453 self.rmenu = rmenu
454
455 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000456 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000457
Steven M. Gava3b55a892001-11-21 05:56:26 +0000458 def config_dialog(self, event=None):
459 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000460
David Scherer7aced172000-08-15 01:13:23 +0000461 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000462 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiserd5f49102007-10-04 02:53:07 +0000463 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000464
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000465 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000466 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000467 try:
468 os.startfile(self.help_url)
469 except WindowsError as why:
470 tkMessageBox.showerror(title='Document Start Failure',
471 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000472 else:
473 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000474 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000475
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000476 def cut(self,event):
477 self.text.event_generate("<<Cut>>")
478 return "break"
479
480 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000481 if not self.text.tag_ranges("sel"):
482 # There is no selection, so do nothing and maybe interrupt.
483 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000484 self.text.event_generate("<<Copy>>")
485 return "break"
486
487 def paste(self,event):
488 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000489 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000490 return "break"
491
David Scherer7aced172000-08-15 01:13:23 +0000492 def select_all(self, event=None):
493 self.text.tag_add("sel", "1.0", "end-1c")
494 self.text.mark_set("insert", "1.0")
495 self.text.see("insert")
496 return "break"
497
498 def remove_selection(self, event=None):
499 self.text.tag_remove("sel", "1.0", "end")
500 self.text.see("insert")
501
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000502 def move_at_edge_if_selection(self, edge_index):
503 """Cursor move begins at start or end of selection
504
505 When a left/right cursor key is pressed create and return to Tkinter a
506 function which causes a cursor move from the associated edge of the
507 selection.
508
509 """
510 self_text_index = self.text.index
511 self_text_mark_set = self.text.mark_set
512 edges_table = ("sel.first+1c", "sel.last-1c")
513 def move_at_edge(event):
514 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
515 try:
516 self_text_index("sel.first")
517 self_text_mark_set("insert", edges_table[edge_index])
518 except TclError:
519 pass
520 return move_at_edge
521
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000522 def del_word_left(self, event):
523 self.text.event_generate('<Meta-Delete>')
524 return "break"
525
526 def del_word_right(self, event):
527 self.text.event_generate('<Meta-d>')
528 return "break"
529
Steven M. Gavac5976402002-01-04 03:06:08 +0000530 def find_event(self, event):
531 SearchDialog.find(self.text)
532 return "break"
533
534 def find_again_event(self, event):
535 SearchDialog.find_again(self.text)
536 return "break"
537
538 def find_selection_event(self, event):
539 SearchDialog.find_selection(self.text)
540 return "break"
541
542 def find_in_files_event(self, event):
543 GrepDialog.grep(self.text, self.io, self.flist)
544 return "break"
545
546 def replace_event(self, event):
547 ReplaceDialog.replace(self.text)
548 return "break"
549
550 def goto_line_event(self, event):
551 text = self.text
552 lineno = tkSimpleDialog.askinteger("Goto",
553 "Go to line number:",parent=text)
554 if lineno is None:
555 return "break"
556 if lineno <= 0:
557 text.bell()
558 return "break"
559 text.mark_set("insert", "%d.0" % lineno)
560 text.see("insert")
561
David Scherer7aced172000-08-15 01:13:23 +0000562 def open_module(self, event=None):
563 # XXX Shouldn't this be in IOBinding or in FileList?
564 try:
565 name = self.text.get("sel.first", "sel.last")
566 except TclError:
567 name = ""
568 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000569 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000570 name = tkSimpleDialog.askstring("Module",
571 "Enter the name of a Python module\n"
572 "to search on sys.path and open:",
573 parent=self.text, initialvalue=name)
574 if name:
575 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000576 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000577 return
David Scherer7aced172000-08-15 01:13:23 +0000578 # XXX Ought to insert current file's directory in front of path
579 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000580 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000581 except (NameError, ImportError), msg:
582 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
583 return
584 if type != imp.PY_SOURCE:
585 tkMessageBox.showerror("Unsupported type",
586 "%s is not a source module" % name, parent=self.text)
587 return
588 if f:
589 f.close()
590 if self.flist:
591 self.flist.open(file)
592 else:
593 self.io.loadfile(file)
594
595 def open_class_browser(self, event=None):
596 filename = self.io.filename
597 if not filename:
598 tkMessageBox.showerror(
599 "No filename",
600 "This buffer has no associated filename",
601 master=self.text)
602 self.text.focus_set()
603 return None
604 head, tail = os.path.split(filename)
605 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000606 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000607 ClassBrowser.ClassBrowser(self.flist, base, [head])
608
609 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000610 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000611 PathBrowser.PathBrowser(self.flist)
612
613 def gotoline(self, lineno):
614 if lineno is not None and lineno > 0:
615 self.text.mark_set("insert", "%d.0" % lineno)
616 self.text.tag_remove("sel", "1.0", "end")
617 self.text.tag_add("sel", "insert", "insert +1l")
618 self.center()
619
620 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000621 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000622 return True
David Scherer7aced172000-08-15 01:13:23 +0000623 base, ext = os.path.splitext(os.path.basename(filename))
624 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000625 return True
David Scherer7aced172000-08-15 01:13:23 +0000626 try:
627 f = open(filename)
628 line = f.readline()
629 f.close()
630 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000631 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000632 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000633
634 def close_hook(self):
635 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000636 self.flist.unregister_maybe_terminate(self)
637 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000638
639 def set_close_hook(self, close_hook):
640 self.close_hook = close_hook
641
642 def filename_change_hook(self):
643 if self.flist:
644 self.flist.filename_changed_edit(self)
645 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000646 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000647 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000648
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000649 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000650 if self.color:
651 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000652 if self.ispythonsource(self.io.filename):
653 self.color = self.ColorDelegator()
654 # can add more colorizers here...
655 if self.color:
656 self.per.removefilter(self.undo)
657 self.per.insertfilter(self.color)
658 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000659
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000660 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000661 if not self.color:
662 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000663 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000664 self.per.removefilter(self.color)
665 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000666
Steven M. Gavab77d3432002-03-02 07:16:21 +0000667 def ResetColorizer(self):
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000668 "Update the colour theme"
669 # Called from self.filename_change_hook and from configDialog.py
670 self._rmcolorizer()
671 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000672 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000673 normal_colors = idleConf.GetHighlight(theme, 'normal')
674 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
675 select_colors = idleConf.GetHighlight(theme, 'hilite')
676 self.text.config(
677 foreground=normal_colors['foreground'],
678 background=normal_colors['background'],
679 insertbackground=cursor_color,
680 selectforeground=select_colors['foreground'],
681 selectbackground=select_colors['background'],
682 )
David Scherer7aced172000-08-15 01:13:23 +0000683
Steven M. Gavab1585412002-03-12 00:21:56 +0000684 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000685 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000686 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000687 fontWeight='normal'
688 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
689 fontWeight='bold'
690 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
691 idleConf.GetOption('main','EditorWindow','font-size'),
692 fontWeight))
693
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000694 def RemoveKeybindings(self):
695 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000696 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000697 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000698 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000699 self.text.event_delete(event, *keylist)
700 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000701 xkeydefs = idleConf.GetExtensionBindings(extensionName)
702 if xkeydefs:
703 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000704 self.text.event_delete(event, *keylist)
705
706 def ApplyKeybindings(self):
707 "Update the keybindings after they are changed"
708 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000709 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000710 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000711 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000712 xkeydefs = idleConf.GetExtensionBindings(extensionName)
713 if xkeydefs:
714 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000715 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000716 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000717 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000718 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000719 for item in menu[1]:
720 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000721 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000722 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000723 menu = self.menudict[menubarItem]
724 end = menu.index(END) + 1
725 for index in range(0, end):
726 if menu.type(index) == 'command':
727 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000728 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000729 itemName = menu.entrycget(index, 'label')
730 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000731 if menubarItem in menuEventDict:
732 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000733 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000734 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000735 accel = get_accelerator(keydefs, event)
736 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000737
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000738 def set_notabs_indentwidth(self):
739 "Update the indentwidth if changed and not using tabs in this window"
740 # Called from configDialog.py
741 if not self.usetabs:
742 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
743 type='int')
744
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000745 def reset_help_menu_entries(self):
746 "Update the additional help entries on the Help menu"
747 help_list = idleConf.GetAllExtraHelpSourcesList()
748 helpmenu = self.menudict['help']
749 # first delete the extra help entries, if any
750 helpmenu_length = helpmenu.index(END)
751 if helpmenu_length > self.base_helpmenu_length:
752 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
753 # then rebuild them
754 if help_list:
755 helpmenu.add_separator()
756 for entry in help_list:
757 cmd = self.__extra_help_callback(entry[1])
758 helpmenu.add_command(label=entry[0], command=cmd)
759 # and update the menu dictionary
760 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000761
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000762 def __extra_help_callback(self, helpfile):
763 "Create a callback with the helpfile value frozen at definition time"
764 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000765 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000766 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000767 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000768 try:
769 os.startfile(helpfile)
770 except WindowsError as why:
771 tkMessageBox.showerror(title='Document Start Failure',
772 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000773 else:
774 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000775 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000776
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000777 def update_recent_files_list(self, new_file=None):
778 "Load and update the recent files list and menus"
779 rf_list = []
780 if os.path.exists(self.recent_files_path):
781 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000782 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000783 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000784 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000785 rf_list_file.close()
786 if new_file:
787 new_file = os.path.abspath(new_file) + '\n'
788 if new_file in rf_list:
789 rf_list.remove(new_file) # move to top
790 rf_list.insert(0, new_file)
791 # clean and save the recent files list
792 bad_paths = []
793 for path in rf_list:
794 if '\0' in path or not os.path.exists(path[0:-1]):
795 bad_paths.append(path)
796 rf_list = [path for path in rf_list if path not in bad_paths]
797 ulchars = "1234567890ABCDEFGHIJK"
798 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000799 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800800 with open(self.recent_files_path, 'w') as rf_file:
801 rf_file.writelines(rf_list)
802 except IOError as err:
803 if not getattr(self.root, "recentfilelist_error_displayed", False):
804 self.root.recentfilelist_error_displayed = True
805 tkMessageBox.showerror(title='IDLE Error',
806 message='Unable to update Recent Files list:\n%s'
807 % str(err),
808 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000809 # for each edit window instance, construct the recent files menu
810 for instance in self.top.instance_dict.keys():
811 menu = instance.recent_files_menu
812 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000813 for i, file_name in enumerate(rf_list):
814 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000815 # make unicode string to display non-ASCII chars correctly
816 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000817 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000818 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000819 command=callback,
820 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000821
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000822 def __recent_file_callback(self, file_name):
823 def open_recent_file(fn_closure=file_name):
824 self.io.open(editFile=fn_closure)
825 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000826
David Scherer7aced172000-08-15 01:13:23 +0000827 def saved_change_hook(self):
828 short = self.short_title()
829 long = self.long_title()
830 if short and long:
831 title = short + " - " + long
832 elif short:
833 title = short
834 elif long:
835 title = long
836 else:
837 title = "Untitled"
838 icon = short or long or title
839 if not self.get_saved():
840 title = "*%s*" % title
841 icon = "*%s" % icon
842 self.top.wm_title(title)
843 self.top.wm_iconname(icon)
844
845 def get_saved(self):
846 return self.undo.get_saved()
847
848 def set_saved(self, flag):
849 self.undo.set_saved(flag)
850
851 def reset_undo(self):
852 self.undo.reset_undo()
853
854 def short_title(self):
855 filename = self.io.filename
856 if filename:
857 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000858 # return unicode string to display non-ASCII chars correctly
859 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000860
861 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000862 # return unicode string to display non-ASCII chars correctly
863 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000864
865 def center_insert_event(self, event):
866 self.center()
867
868 def center(self, mark="insert"):
869 text = self.text
870 top, bot = self.getwindowlines()
871 lineno = self.getlineno(mark)
872 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000873 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000874 text.yview(float(newtop))
875
876 def getwindowlines(self):
877 text = self.text
878 top = self.getlineno("@0,0")
879 bot = self.getlineno("@0,65535")
880 if top == bot and text.winfo_height() == 1:
881 # Geometry manager hasn't run yet
882 height = int(text['height'])
883 bot = top + height - 1
884 return top, bot
885
886 def getlineno(self, mark="insert"):
887 text = self.text
888 return int(float(text.index(mark)))
889
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000890 def get_geometry(self):
891 "Return (width, height, x, y)"
892 geom = self.top.wm_geometry()
893 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
894 tuple = (map(int, m.groups()))
895 return tuple
896
David Scherer7aced172000-08-15 01:13:23 +0000897 def close_event(self, event):
898 self.close()
899
900 def maybesave(self):
901 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000902 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000903 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000904 self.top.deiconify()
905 self.top.lower()
906 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000907 return self.io.maybesave()
908
909 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000910 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +0000911 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000912 self._close()
913 return reply
914
915 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000916 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000917 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000918 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000919 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000920 self.io.close()
921 self.io = None
922 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000923 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000924 self.color.close(False)
925 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000926 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000927 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000928 self.per.close()
929 self.per = None
930 self.top.destroy()
931 if self.close_hook:
932 # unless override: unregister from flist, terminate if last window
933 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000934
935 def load_extensions(self):
936 self.extensions = {}
937 self.load_standard_extensions()
938
939 def unload_extensions(self):
940 for ins in self.extensions.values():
941 if hasattr(ins, "close"):
942 ins.close()
943 self.extensions = {}
944
945 def load_standard_extensions(self):
946 for name in self.get_standard_extension_names():
947 try:
948 self.load_extension(name)
949 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000950 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +0000951 import traceback
952 traceback.print_exc()
953
954 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000955 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000956
957 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000958 try:
959 mod = __import__(name, globals(), locals(), [])
960 except ImportError:
961 print "\nFailed to import extension: ", name
962 return
David Scherer7aced172000-08-15 01:13:23 +0000963 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000964 keydefs = idleConf.GetExtensionBindings(name)
965 if hasattr(cls, "menudefs"):
966 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000967 ins = cls(self)
968 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000969 if keydefs:
970 self.apply_bindings(keydefs)
971 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000972 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000973 while methodname[:1] == '<':
974 methodname = methodname[1:]
975 while methodname[-1:] == '>':
976 methodname = methodname[:-1]
977 methodname = methodname + "_event"
978 if hasattr(ins, methodname):
979 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000980
981 def apply_bindings(self, keydefs=None):
982 if keydefs is None:
983 keydefs = self.Bindings.default_keydefs
984 text = self.text
985 text.keydefs = keydefs
986 for event, keylist in keydefs.items():
987 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000988 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000989
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000990 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000991 """Add appropriate entries to the menus and submenus
992
993 Menus that are absent or None in self.menudict are ignored.
994 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000995 if menudefs is None:
996 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000997 if keydefs is None:
998 keydefs = self.Bindings.default_keydefs
999 menudict = self.menudict
1000 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001001 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001002 menu = menudict.get(mname)
1003 if not menu:
1004 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001005 for entry in entrylist:
1006 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001007 menu.add_separator()
1008 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001009 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001010 checkbutton = (label[:1] == '!')
1011 if checkbutton:
1012 label = label[1:]
1013 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001014 accelerator = get_accelerator(keydefs, eventname)
1015 def command(text=text, eventname=eventname):
1016 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001017 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001018 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001019 menu.add_checkbutton(label=label, underline=underline,
1020 command=command, accelerator=accelerator,
1021 variable=var)
1022 else:
1023 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001024 command=command,
1025 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001026
1027 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001028 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001029 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001030 value = var.get()
1031 return value
1032 else:
1033 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001034
1035 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001036 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001037 if var:
1038 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001039 else:
1040 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001041
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001042 def get_var_obj(self, name, vartype=None):
1043 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001044 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001045 # create a Tkinter variable object with self.text as master:
1046 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001047 return var
1048
1049 # Tk implementations of "virtual text methods" -- each platform
1050 # reusing IDLE's support code needs to define these for its GUI's
1051 # flavor of widget.
1052
1053 # Is character at text_index in a Python string? Return 0 for
1054 # "guaranteed no", true for anything else. This info is expensive
1055 # to compute ab initio, but is probably already known by the
1056 # platform's colorizer.
1057
1058 def is_char_in_string(self, text_index):
1059 if self.color:
1060 # Return true iff colorizer hasn't (re)gotten this far
1061 # yet, or the character is tagged as being in a string
1062 return self.text.tag_prevrange("TODO", text_index) or \
1063 "STRING" in self.text.tag_names(text_index)
1064 else:
1065 # The colorizer is missing: assume the worst
1066 return 1
1067
1068 # If a selection is defined in the text widget, return (start,
1069 # end) as Tkinter text indices, otherwise return (None, None)
1070 def get_selection_indices(self):
1071 try:
1072 first = self.text.index("sel.first")
1073 last = self.text.index("sel.last")
1074 return first, last
1075 except TclError:
1076 return None, None
1077
1078 # Return the text widget's current view of what a tab stop means
1079 # (equivalent width in spaces).
1080
1081 def get_tabwidth(self):
1082 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1083 return int(current)
1084
1085 # Set the text widget's current view of what a tab stop means.
1086
1087 def set_tabwidth(self, newtabwidth):
1088 text = self.text
1089 if self.get_tabwidth() != newtabwidth:
1090 pixels = text.tk.call("font", "measure", text["font"],
1091 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001092 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001093 text.configure(tabs=pixels)
1094
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001095 # If ispythonsource and guess are true, guess a good value for
1096 # indentwidth based on file content (if possible), and if
1097 # indentwidth != tabwidth set usetabs false.
1098 # In any case, adjust the Text widget's view of what a tab
1099 # character means.
1100
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001101 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001102 if guess and ispythonsource:
1103 i = self.guess_indent()
1104 if 2 <= i <= 8:
1105 self.indentwidth = i
1106 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001107 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001108 self.set_tabwidth(self.tabwidth)
1109
1110 def smart_backspace_event(self, event):
1111 text = self.text
1112 first, last = self.get_selection_indices()
1113 if first and last:
1114 text.delete(first, last)
1115 text.mark_set("insert", first)
1116 return "break"
1117 # Delete whitespace left, until hitting a real char or closest
1118 # preceding virtual tab stop.
1119 chars = text.get("insert linestart", "insert")
1120 if chars == '':
1121 if text.compare("insert", ">", "1.0"):
1122 # easy: delete preceding newline
1123 text.delete("insert-1c")
1124 else:
1125 text.bell() # at start of buffer
1126 return "break"
1127 if chars[-1] not in " \t":
1128 # easy: delete preceding real char
1129 text.delete("insert-1c")
1130 return "break"
1131 # Ick. It may require *inserting* spaces if we back up over a
1132 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001133 tabwidth = self.tabwidth
1134 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001135 assert have > 0
1136 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001137 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001138 if self.context_use_ps1:
1139 last_line_of_prompt = sys.ps1.split('\n')[-1]
1140 else:
1141 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001142 ncharsdeleted = 0
1143 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001144 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001145 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001146 chars = chars[:-1]
1147 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001148 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001149 if have <= want or chars[-1] not in " \t":
1150 break
1151 text.undo_block_start()
1152 text.delete("insert-%dc" % ncharsdeleted, "insert")
1153 if have < want:
1154 text.insert("insert", ' ' * (want - have))
1155 text.undo_block_stop()
1156 return "break"
1157
1158 def smart_indent_event(self, event):
1159 # if intraline selection:
1160 # delete it
1161 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001162 # do indent-region
1163 # else:
1164 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001165 text = self.text
1166 first, last = self.get_selection_indices()
1167 text.undo_block_start()
1168 try:
1169 if first and last:
1170 if index2line(first) != index2line(last):
1171 return self.indent_region_event(event)
1172 text.delete(first, last)
1173 text.mark_set("insert", first)
1174 prefix = text.get("insert linestart", "insert")
1175 raw, effective = classifyws(prefix, self.tabwidth)
1176 if raw == len(prefix):
1177 # only whitespace to the left
1178 self.reindent_to(effective + self.indentwidth)
1179 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001180 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001181 if self.usetabs:
1182 pad = '\t'
1183 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001184 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001185 n = self.indentwidth
1186 pad = ' ' * (n - effective % n)
1187 text.insert("insert", pad)
1188 text.see("insert")
1189 return "break"
1190 finally:
1191 text.undo_block_stop()
1192
1193 def newline_and_indent_event(self, event):
1194 text = self.text
1195 first, last = self.get_selection_indices()
1196 text.undo_block_start()
1197 try:
1198 if first and last:
1199 text.delete(first, last)
1200 text.mark_set("insert", first)
1201 line = text.get("insert linestart", "insert")
1202 i, n = 0, len(line)
1203 while i < n and line[i] in " \t":
1204 i = i+1
1205 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001206 # the cursor is in or at leading indentation in a continuation
1207 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001208 text.insert("insert linestart", '\n')
1209 return "break"
1210 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001211 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001212 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001213 last_line_of_prompt = sys.ps1.split('\n')[-1]
1214 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001215 line = line[:-1]
1216 i = i+1
1217 if i:
1218 text.delete("insert - %d chars" % i, "insert")
1219 # strip whitespace after insert point
1220 while text.get("insert") in " \t":
1221 text.delete("insert")
1222 # start new line
1223 text.insert("insert", '\n')
1224
1225 # adjust indentation for continuations and block
1226 # open/close first need to find the last stmt
1227 lno = index2line(text.index('insert'))
1228 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001229 if not self.context_use_ps1:
1230 for context in self.num_context_lines:
1231 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001232 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001233 rawtext = text.get(startatindex, "insert")
1234 y.set_str(rawtext)
1235 bod = y.find_good_parse_start(
1236 self.context_use_ps1,
1237 self._build_char_in_string_func(startatindex))
1238 if bod is not None or startat == 1:
1239 break
1240 y.set_lo(bod or 0)
1241 else:
1242 r = text.tag_prevrange("console", "insert")
1243 if r:
1244 startatindex = r[1]
1245 else:
1246 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001247 rawtext = text.get(startatindex, "insert")
1248 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001249 y.set_lo(0)
1250
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001251 c = y.get_continuation_type()
1252 if c != PyParse.C_NONE:
1253 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001254 if c == PyParse.C_STRING_FIRST_LINE:
1255 # after the first line of a string; do not indent at all
1256 pass
1257 elif c == PyParse.C_STRING_NEXT_LINES:
1258 # inside a string which started before this line;
1259 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001260 text.insert("insert", indent)
1261 elif c == PyParse.C_BRACKET:
1262 # line up with the first (if any) element of the
1263 # last open bracket structure; else indent one
1264 # level beyond the indent of the line with the
1265 # last open bracket
1266 self.reindent_to(y.compute_bracket_indent())
1267 elif c == PyParse.C_BACKSLASH:
1268 # if more than one line in this stmt already, just
1269 # mimic the current indent; else if initial line
1270 # has a start on an assignment stmt, indent to
1271 # beyond leftmost =; else to beyond first chunk of
1272 # non-whitespace on initial line
1273 if y.get_num_lines_in_stmt() > 1:
1274 text.insert("insert", indent)
1275 else:
1276 self.reindent_to(y.compute_backslash_indent())
1277 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001278 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001279 return "break"
1280
1281 # This line starts a brand new stmt; indent relative to
1282 # indentation of initial line of closest preceding
1283 # interesting stmt.
1284 indent = y.get_base_indent_string()
1285 text.insert("insert", indent)
1286 if y.is_block_opener():
1287 self.smart_indent_event(event)
1288 elif indent and y.is_block_closer():
1289 self.smart_backspace_event(event)
1290 return "break"
1291 finally:
1292 text.see("insert")
1293 text.undo_block_stop()
1294
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001295 # Our editwin provides a is_char_in_string function that works
1296 # with a Tk text index, but PyParse only knows about offsets into
1297 # a string. This builds a function for PyParse that accepts an
1298 # offset.
1299
1300 def _build_char_in_string_func(self, startindex):
1301 def inner(offset, _startindex=startindex,
1302 _icis=self.is_char_in_string):
1303 return _icis(_startindex + "+%dc" % offset)
1304 return inner
1305
1306 def indent_region_event(self, event):
1307 head, tail, chars, lines = self.get_region()
1308 for pos in range(len(lines)):
1309 line = lines[pos]
1310 if line:
1311 raw, effective = classifyws(line, self.tabwidth)
1312 effective = effective + self.indentwidth
1313 lines[pos] = self._make_blanks(effective) + line[raw:]
1314 self.set_region(head, tail, chars, lines)
1315 return "break"
1316
1317 def dedent_region_event(self, event):
1318 head, tail, chars, lines = self.get_region()
1319 for pos in range(len(lines)):
1320 line = lines[pos]
1321 if line:
1322 raw, effective = classifyws(line, self.tabwidth)
1323 effective = max(effective - self.indentwidth, 0)
1324 lines[pos] = self._make_blanks(effective) + line[raw:]
1325 self.set_region(head, tail, chars, lines)
1326 return "break"
1327
1328 def comment_region_event(self, event):
1329 head, tail, chars, lines = self.get_region()
1330 for pos in range(len(lines) - 1):
1331 line = lines[pos]
1332 lines[pos] = '##' + line
1333 self.set_region(head, tail, chars, lines)
1334
1335 def uncomment_region_event(self, event):
1336 head, tail, chars, lines = self.get_region()
1337 for pos in range(len(lines)):
1338 line = lines[pos]
1339 if not line:
1340 continue
1341 if line[:2] == '##':
1342 line = line[2:]
1343 elif line[:1] == '#':
1344 line = line[1:]
1345 lines[pos] = line
1346 self.set_region(head, tail, chars, lines)
1347
1348 def tabify_region_event(self, event):
1349 head, tail, chars, lines = self.get_region()
1350 tabwidth = self._asktabwidth()
1351 for pos in range(len(lines)):
1352 line = lines[pos]
1353 if line:
1354 raw, effective = classifyws(line, tabwidth)
1355 ntabs, nspaces = divmod(effective, tabwidth)
1356 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1357 self.set_region(head, tail, chars, lines)
1358
1359 def untabify_region_event(self, event):
1360 head, tail, chars, lines = self.get_region()
1361 tabwidth = self._asktabwidth()
1362 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001363 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001364 self.set_region(head, tail, chars, lines)
1365
1366 def toggle_tabs_event(self, event):
1367 if self.askyesno(
1368 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001369 "Turn tabs " + ("on", "off")[self.usetabs] +
1370 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001371 ("will be", "remains at")[self.usetabs] + " 8." +
1372 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001373 parent=self.text):
1374 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001375 # Try to prevent inconsistent indentation.
1376 # User must change indent width manually after using tabs.
1377 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001378 return "break"
1379
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001380 # XXX this isn't bound to anything -- see tabwidth comments
1381## def change_tabwidth_event(self, event):
1382## new = self._asktabwidth()
1383## if new != self.tabwidth:
1384## self.tabwidth = new
1385## self.set_indentation_params(0, guess=0)
1386## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001387
1388 def change_indentwidth_event(self, event):
1389 new = self.askinteger(
1390 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001391 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001392 parent=self.text,
1393 initialvalue=self.indentwidth,
1394 minvalue=2,
1395 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001396 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001397 self.indentwidth = new
1398 return "break"
1399
1400 def get_region(self):
1401 text = self.text
1402 first, last = self.get_selection_indices()
1403 if first and last:
1404 head = text.index(first + " linestart")
1405 tail = text.index(last + "-1c lineend +1c")
1406 else:
1407 head = text.index("insert linestart")
1408 tail = text.index("insert lineend +1c")
1409 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001410 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001411 return head, tail, chars, lines
1412
1413 def set_region(self, head, tail, chars, lines):
1414 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001415 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001416 if newchars == chars:
1417 text.bell()
1418 return
1419 text.tag_remove("sel", "1.0", "end")
1420 text.mark_set("insert", head)
1421 text.undo_block_start()
1422 text.delete(head, tail)
1423 text.insert(head, newchars)
1424 text.undo_block_stop()
1425 text.tag_add("sel", head, "insert")
1426
1427 # Make string that displays as n leading blanks.
1428
1429 def _make_blanks(self, n):
1430 if self.usetabs:
1431 ntabs, nspaces = divmod(n, self.tabwidth)
1432 return '\t' * ntabs + ' ' * nspaces
1433 else:
1434 return ' ' * n
1435
1436 # Delete from beginning of line to insert point, then reinsert
1437 # column logical (meaning use tabs if appropriate) spaces.
1438
1439 def reindent_to(self, column):
1440 text = self.text
1441 text.undo_block_start()
1442 if text.compare("insert linestart", "!=", "insert"):
1443 text.delete("insert linestart", "insert")
1444 if column:
1445 text.insert("insert", self._make_blanks(column))
1446 text.undo_block_stop()
1447
1448 def _asktabwidth(self):
1449 return self.askinteger(
1450 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001451 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001452 parent=self.text,
1453 initialvalue=self.indentwidth,
1454 minvalue=2,
1455 maxvalue=16) or self.tabwidth
1456
1457 # Guess indentwidth from text content.
1458 # Return guessed indentwidth. This should not be believed unless
1459 # it's in a reasonable range (e.g., it will be 0 if no indented
1460 # blocks are found).
1461
1462 def guess_indent(self):
1463 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1464 if opener and indented:
1465 raw, indentsmall = classifyws(opener, self.tabwidth)
1466 raw, indentlarge = classifyws(indented, self.tabwidth)
1467 else:
1468 indentsmall = indentlarge = 0
1469 return indentlarge - indentsmall
1470
1471# "line.col" -> line, as an int
1472def index2line(index):
1473 return int(float(index))
1474
1475# Look at the leading whitespace in s.
1476# Return pair (# of leading ws characters,
1477# effective # of leading blanks after expanding
1478# tabs to width tabwidth)
1479
1480def classifyws(s, tabwidth):
1481 raw = effective = 0
1482 for ch in s:
1483 if ch == ' ':
1484 raw = raw + 1
1485 effective = effective + 1
1486 elif ch == '\t':
1487 raw = raw + 1
1488 effective = (effective // tabwidth + 1) * tabwidth
1489 else:
1490 break
1491 return raw, effective
1492
1493import tokenize
1494_tokenize = tokenize
1495del tokenize
1496
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001497class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001498
1499 # .run() chews over the Text widget, looking for a block opener
1500 # and the stmt following it. Returns a pair,
1501 # (line containing block opener, line containing stmt)
1502 # Either or both may be None.
1503
1504 def __init__(self, text, tabwidth):
1505 self.text = text
1506 self.tabwidth = tabwidth
1507 self.i = self.finished = 0
1508 self.blkopenline = self.indentedline = None
1509
1510 def readline(self):
1511 if self.finished:
1512 return ""
1513 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001514 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001515 if self.text.compare(mark, ">=", "end"):
1516 return ""
1517 return self.text.get(mark, mark + " lineend+1c")
1518
1519 def tokeneater(self, type, token, start, end, line,
1520 INDENT=_tokenize.INDENT,
1521 NAME=_tokenize.NAME,
1522 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1523 if self.finished:
1524 pass
1525 elif type == NAME and token in OPENERS:
1526 self.blkopenline = line
1527 elif type == INDENT and self.blkopenline:
1528 self.indentedline = line
1529 self.finished = 1
1530
1531 def run(self):
1532 save_tabsize = _tokenize.tabsize
1533 _tokenize.tabsize = self.tabwidth
1534 try:
1535 try:
1536 _tokenize.tokenize(self.readline, self.tokeneater)
1537 except _tokenize.TokenError:
1538 # since we cut off the tokenizer early, we can trigger
1539 # spurious errors
1540 pass
1541 finally:
1542 _tokenize.tabsize = save_tabsize
1543 return self.blkopenline, self.indentedline
1544
1545### end autoindent code ###
1546
David Scherer7aced172000-08-15 01:13:23 +00001547def prepstr(s):
1548 # Helper to extract the underscore from a string, e.g.
1549 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001550 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001551 if i >= 0:
1552 s = s[:i] + s[i+1:]
1553 return i, s
1554
1555
1556keynames = {
1557 'bracketleft': '[',
1558 'bracketright': ']',
1559 'slash': '/',
1560}
1561
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001562def get_accelerator(keydefs, eventname):
1563 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001564 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1565 # if not keylist:
1566 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1567 "<<open-module>>",
1568 "<<goto-line>>",
1569 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001570 return ""
1571 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001572 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001573 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1574 s = re.sub("Key-", "", s)
1575 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1576 s = re.sub("Control-", "Ctrl-", s)
1577 s = re.sub("-", "+", s)
1578 s = re.sub("><", " ", s)
1579 s = re.sub("<", "", s)
1580 s = re.sub(">", "", s)
1581 return s
1582
1583
1584def fixwordbreaks(root):
1585 # Make sure that Tk's double-click and next/previous word
1586 # operations use our definition of a word (i.e. an identifier)
1587 tk = root.tk
1588 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1589 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1590 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1591
1592
1593def test():
1594 root = Tk()
1595 fixwordbreaks(root)
1596 root.withdraw()
1597 if sys.argv[1:]:
1598 filename = sys.argv[1]
1599 else:
1600 filename = None
1601 edit = EditorWindow(root=root, filename=filename)
1602 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001603 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001604 root.mainloop()
1605 root.destroy()
1606
1607if __name__ == '__main__':
1608 test()