blob: f16badb90e2d33b27e4fa448e776b3a78ac16494 [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....
1138 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001139 ncharsdeleted = 0
1140 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001141 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001142 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001143 chars = chars[:-1]
1144 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001145 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001146 if have <= want or chars[-1] not in " \t":
1147 break
1148 text.undo_block_start()
1149 text.delete("insert-%dc" % ncharsdeleted, "insert")
1150 if have < want:
1151 text.insert("insert", ' ' * (want - have))
1152 text.undo_block_stop()
1153 return "break"
1154
1155 def smart_indent_event(self, event):
1156 # if intraline selection:
1157 # delete it
1158 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001159 # do indent-region
1160 # else:
1161 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001162 text = self.text
1163 first, last = self.get_selection_indices()
1164 text.undo_block_start()
1165 try:
1166 if first and last:
1167 if index2line(first) != index2line(last):
1168 return self.indent_region_event(event)
1169 text.delete(first, last)
1170 text.mark_set("insert", first)
1171 prefix = text.get("insert linestart", "insert")
1172 raw, effective = classifyws(prefix, self.tabwidth)
1173 if raw == len(prefix):
1174 # only whitespace to the left
1175 self.reindent_to(effective + self.indentwidth)
1176 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001177 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001178 if self.usetabs:
1179 pad = '\t'
1180 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001181 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001182 n = self.indentwidth
1183 pad = ' ' * (n - effective % n)
1184 text.insert("insert", pad)
1185 text.see("insert")
1186 return "break"
1187 finally:
1188 text.undo_block_stop()
1189
1190 def newline_and_indent_event(self, event):
1191 text = self.text
1192 first, last = self.get_selection_indices()
1193 text.undo_block_start()
1194 try:
1195 if first and last:
1196 text.delete(first, last)
1197 text.mark_set("insert", first)
1198 line = text.get("insert linestart", "insert")
1199 i, n = 0, len(line)
1200 while i < n and line[i] in " \t":
1201 i = i+1
1202 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001203 # the cursor is in or at leading indentation in a continuation
1204 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001205 text.insert("insert linestart", '\n')
1206 return "break"
1207 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001208 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001209 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001210 last_line_of_prompt = sys.ps1.split('\n')[-1]
1211 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001212 line = line[:-1]
1213 i = i+1
1214 if i:
1215 text.delete("insert - %d chars" % i, "insert")
1216 # strip whitespace after insert point
1217 while text.get("insert") in " \t":
1218 text.delete("insert")
1219 # start new line
1220 text.insert("insert", '\n')
1221
1222 # adjust indentation for continuations and block
1223 # open/close first need to find the last stmt
1224 lno = index2line(text.index('insert'))
1225 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001226 if not self.context_use_ps1:
1227 for context in self.num_context_lines:
1228 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001229 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001230 rawtext = text.get(startatindex, "insert")
1231 y.set_str(rawtext)
1232 bod = y.find_good_parse_start(
1233 self.context_use_ps1,
1234 self._build_char_in_string_func(startatindex))
1235 if bod is not None or startat == 1:
1236 break
1237 y.set_lo(bod or 0)
1238 else:
1239 r = text.tag_prevrange("console", "insert")
1240 if r:
1241 startatindex = r[1]
1242 else:
1243 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001244 rawtext = text.get(startatindex, "insert")
1245 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001246 y.set_lo(0)
1247
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001248 c = y.get_continuation_type()
1249 if c != PyParse.C_NONE:
1250 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001251 if c == PyParse.C_STRING_FIRST_LINE:
1252 # after the first line of a string; do not indent at all
1253 pass
1254 elif c == PyParse.C_STRING_NEXT_LINES:
1255 # inside a string which started before this line;
1256 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001257 text.insert("insert", indent)
1258 elif c == PyParse.C_BRACKET:
1259 # line up with the first (if any) element of the
1260 # last open bracket structure; else indent one
1261 # level beyond the indent of the line with the
1262 # last open bracket
1263 self.reindent_to(y.compute_bracket_indent())
1264 elif c == PyParse.C_BACKSLASH:
1265 # if more than one line in this stmt already, just
1266 # mimic the current indent; else if initial line
1267 # has a start on an assignment stmt, indent to
1268 # beyond leftmost =; else to beyond first chunk of
1269 # non-whitespace on initial line
1270 if y.get_num_lines_in_stmt() > 1:
1271 text.insert("insert", indent)
1272 else:
1273 self.reindent_to(y.compute_backslash_indent())
1274 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001275 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001276 return "break"
1277
1278 # This line starts a brand new stmt; indent relative to
1279 # indentation of initial line of closest preceding
1280 # interesting stmt.
1281 indent = y.get_base_indent_string()
1282 text.insert("insert", indent)
1283 if y.is_block_opener():
1284 self.smart_indent_event(event)
1285 elif indent and y.is_block_closer():
1286 self.smart_backspace_event(event)
1287 return "break"
1288 finally:
1289 text.see("insert")
1290 text.undo_block_stop()
1291
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001292 # Our editwin provides a is_char_in_string function that works
1293 # with a Tk text index, but PyParse only knows about offsets into
1294 # a string. This builds a function for PyParse that accepts an
1295 # offset.
1296
1297 def _build_char_in_string_func(self, startindex):
1298 def inner(offset, _startindex=startindex,
1299 _icis=self.is_char_in_string):
1300 return _icis(_startindex + "+%dc" % offset)
1301 return inner
1302
1303 def indent_region_event(self, event):
1304 head, tail, chars, lines = self.get_region()
1305 for pos in range(len(lines)):
1306 line = lines[pos]
1307 if line:
1308 raw, effective = classifyws(line, self.tabwidth)
1309 effective = effective + self.indentwidth
1310 lines[pos] = self._make_blanks(effective) + line[raw:]
1311 self.set_region(head, tail, chars, lines)
1312 return "break"
1313
1314 def dedent_region_event(self, event):
1315 head, tail, chars, lines = self.get_region()
1316 for pos in range(len(lines)):
1317 line = lines[pos]
1318 if line:
1319 raw, effective = classifyws(line, self.tabwidth)
1320 effective = max(effective - self.indentwidth, 0)
1321 lines[pos] = self._make_blanks(effective) + line[raw:]
1322 self.set_region(head, tail, chars, lines)
1323 return "break"
1324
1325 def comment_region_event(self, event):
1326 head, tail, chars, lines = self.get_region()
1327 for pos in range(len(lines) - 1):
1328 line = lines[pos]
1329 lines[pos] = '##' + line
1330 self.set_region(head, tail, chars, lines)
1331
1332 def uncomment_region_event(self, event):
1333 head, tail, chars, lines = self.get_region()
1334 for pos in range(len(lines)):
1335 line = lines[pos]
1336 if not line:
1337 continue
1338 if line[:2] == '##':
1339 line = line[2:]
1340 elif line[:1] == '#':
1341 line = line[1:]
1342 lines[pos] = line
1343 self.set_region(head, tail, chars, lines)
1344
1345 def tabify_region_event(self, event):
1346 head, tail, chars, lines = self.get_region()
1347 tabwidth = self._asktabwidth()
1348 for pos in range(len(lines)):
1349 line = lines[pos]
1350 if line:
1351 raw, effective = classifyws(line, tabwidth)
1352 ntabs, nspaces = divmod(effective, tabwidth)
1353 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1354 self.set_region(head, tail, chars, lines)
1355
1356 def untabify_region_event(self, event):
1357 head, tail, chars, lines = self.get_region()
1358 tabwidth = self._asktabwidth()
1359 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001360 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001361 self.set_region(head, tail, chars, lines)
1362
1363 def toggle_tabs_event(self, event):
1364 if self.askyesno(
1365 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001366 "Turn tabs " + ("on", "off")[self.usetabs] +
1367 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001368 ("will be", "remains at")[self.usetabs] + " 8." +
1369 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001370 parent=self.text):
1371 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001372 # Try to prevent inconsistent indentation.
1373 # User must change indent width manually after using tabs.
1374 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001375 return "break"
1376
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001377 # XXX this isn't bound to anything -- see tabwidth comments
1378## def change_tabwidth_event(self, event):
1379## new = self._asktabwidth()
1380## if new != self.tabwidth:
1381## self.tabwidth = new
1382## self.set_indentation_params(0, guess=0)
1383## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001384
1385 def change_indentwidth_event(self, event):
1386 new = self.askinteger(
1387 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001388 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001389 parent=self.text,
1390 initialvalue=self.indentwidth,
1391 minvalue=2,
1392 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001393 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001394 self.indentwidth = new
1395 return "break"
1396
1397 def get_region(self):
1398 text = self.text
1399 first, last = self.get_selection_indices()
1400 if first and last:
1401 head = text.index(first + " linestart")
1402 tail = text.index(last + "-1c lineend +1c")
1403 else:
1404 head = text.index("insert linestart")
1405 tail = text.index("insert lineend +1c")
1406 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001407 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001408 return head, tail, chars, lines
1409
1410 def set_region(self, head, tail, chars, lines):
1411 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001412 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001413 if newchars == chars:
1414 text.bell()
1415 return
1416 text.tag_remove("sel", "1.0", "end")
1417 text.mark_set("insert", head)
1418 text.undo_block_start()
1419 text.delete(head, tail)
1420 text.insert(head, newchars)
1421 text.undo_block_stop()
1422 text.tag_add("sel", head, "insert")
1423
1424 # Make string that displays as n leading blanks.
1425
1426 def _make_blanks(self, n):
1427 if self.usetabs:
1428 ntabs, nspaces = divmod(n, self.tabwidth)
1429 return '\t' * ntabs + ' ' * nspaces
1430 else:
1431 return ' ' * n
1432
1433 # Delete from beginning of line to insert point, then reinsert
1434 # column logical (meaning use tabs if appropriate) spaces.
1435
1436 def reindent_to(self, column):
1437 text = self.text
1438 text.undo_block_start()
1439 if text.compare("insert linestart", "!=", "insert"):
1440 text.delete("insert linestart", "insert")
1441 if column:
1442 text.insert("insert", self._make_blanks(column))
1443 text.undo_block_stop()
1444
1445 def _asktabwidth(self):
1446 return self.askinteger(
1447 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001448 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001449 parent=self.text,
1450 initialvalue=self.indentwidth,
1451 minvalue=2,
1452 maxvalue=16) or self.tabwidth
1453
1454 # Guess indentwidth from text content.
1455 # Return guessed indentwidth. This should not be believed unless
1456 # it's in a reasonable range (e.g., it will be 0 if no indented
1457 # blocks are found).
1458
1459 def guess_indent(self):
1460 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1461 if opener and indented:
1462 raw, indentsmall = classifyws(opener, self.tabwidth)
1463 raw, indentlarge = classifyws(indented, self.tabwidth)
1464 else:
1465 indentsmall = indentlarge = 0
1466 return indentlarge - indentsmall
1467
1468# "line.col" -> line, as an int
1469def index2line(index):
1470 return int(float(index))
1471
1472# Look at the leading whitespace in s.
1473# Return pair (# of leading ws characters,
1474# effective # of leading blanks after expanding
1475# tabs to width tabwidth)
1476
1477def classifyws(s, tabwidth):
1478 raw = effective = 0
1479 for ch in s:
1480 if ch == ' ':
1481 raw = raw + 1
1482 effective = effective + 1
1483 elif ch == '\t':
1484 raw = raw + 1
1485 effective = (effective // tabwidth + 1) * tabwidth
1486 else:
1487 break
1488 return raw, effective
1489
1490import tokenize
1491_tokenize = tokenize
1492del tokenize
1493
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001494class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001495
1496 # .run() chews over the Text widget, looking for a block opener
1497 # and the stmt following it. Returns a pair,
1498 # (line containing block opener, line containing stmt)
1499 # Either or both may be None.
1500
1501 def __init__(self, text, tabwidth):
1502 self.text = text
1503 self.tabwidth = tabwidth
1504 self.i = self.finished = 0
1505 self.blkopenline = self.indentedline = None
1506
1507 def readline(self):
1508 if self.finished:
1509 return ""
1510 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001511 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001512 if self.text.compare(mark, ">=", "end"):
1513 return ""
1514 return self.text.get(mark, mark + " lineend+1c")
1515
1516 def tokeneater(self, type, token, start, end, line,
1517 INDENT=_tokenize.INDENT,
1518 NAME=_tokenize.NAME,
1519 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1520 if self.finished:
1521 pass
1522 elif type == NAME and token in OPENERS:
1523 self.blkopenline = line
1524 elif type == INDENT and self.blkopenline:
1525 self.indentedline = line
1526 self.finished = 1
1527
1528 def run(self):
1529 save_tabsize = _tokenize.tabsize
1530 _tokenize.tabsize = self.tabwidth
1531 try:
1532 try:
1533 _tokenize.tokenize(self.readline, self.tokeneater)
1534 except _tokenize.TokenError:
1535 # since we cut off the tokenizer early, we can trigger
1536 # spurious errors
1537 pass
1538 finally:
1539 _tokenize.tabsize = save_tabsize
1540 return self.blkopenline, self.indentedline
1541
1542### end autoindent code ###
1543
David Scherer7aced172000-08-15 01:13:23 +00001544def prepstr(s):
1545 # Helper to extract the underscore from a string, e.g.
1546 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001547 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001548 if i >= 0:
1549 s = s[:i] + s[i+1:]
1550 return i, s
1551
1552
1553keynames = {
1554 'bracketleft': '[',
1555 'bracketright': ']',
1556 'slash': '/',
1557}
1558
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001559def get_accelerator(keydefs, eventname):
1560 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001561 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1562 # if not keylist:
1563 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1564 "<<open-module>>",
1565 "<<goto-line>>",
1566 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001567 return ""
1568 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001569 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001570 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1571 s = re.sub("Key-", "", s)
1572 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1573 s = re.sub("Control-", "Ctrl-", s)
1574 s = re.sub("-", "+", s)
1575 s = re.sub("><", " ", s)
1576 s = re.sub("<", "", s)
1577 s = re.sub(">", "", s)
1578 return s
1579
1580
1581def fixwordbreaks(root):
1582 # Make sure that Tk's double-click and next/previous word
1583 # operations use our definition of a word (i.e. an identifier)
1584 tk = root.tk
1585 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1586 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1587 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1588
1589
1590def test():
1591 root = Tk()
1592 fixwordbreaks(root)
1593 root.withdraw()
1594 if sys.argv[1:]:
1595 filename = sys.argv[1]
1596 else:
1597 filename = None
1598 edit = EditorWindow(root=root, filename=filename)
1599 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001600 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001601 root.mainloop()
1602 root.destroy()
1603
1604if __name__ == '__main__':
1605 test()