blob: 77dcd501e718b48efe699f73c19a490e3b88f41d [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)]
799 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000800 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000801 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000802 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000803 rf_file.close()
804 # for each edit window instance, construct the recent files menu
805 for instance in self.top.instance_dict.keys():
806 menu = instance.recent_files_menu
807 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000808 for i, file_name in enumerate(rf_list):
809 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000810 # make unicode string to display non-ASCII chars correctly
811 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000812 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000813 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000814 command=callback,
815 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000816
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000817 def __recent_file_callback(self, file_name):
818 def open_recent_file(fn_closure=file_name):
819 self.io.open(editFile=fn_closure)
820 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000821
David Scherer7aced172000-08-15 01:13:23 +0000822 def saved_change_hook(self):
823 short = self.short_title()
824 long = self.long_title()
825 if short and long:
826 title = short + " - " + long
827 elif short:
828 title = short
829 elif long:
830 title = long
831 else:
832 title = "Untitled"
833 icon = short or long or title
834 if not self.get_saved():
835 title = "*%s*" % title
836 icon = "*%s" % icon
837 self.top.wm_title(title)
838 self.top.wm_iconname(icon)
839
840 def get_saved(self):
841 return self.undo.get_saved()
842
843 def set_saved(self, flag):
844 self.undo.set_saved(flag)
845
846 def reset_undo(self):
847 self.undo.reset_undo()
848
849 def short_title(self):
850 filename = self.io.filename
851 if filename:
852 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000853 # return unicode string to display non-ASCII chars correctly
854 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000855
856 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000857 # return unicode string to display non-ASCII chars correctly
858 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000859
860 def center_insert_event(self, event):
861 self.center()
862
863 def center(self, mark="insert"):
864 text = self.text
865 top, bot = self.getwindowlines()
866 lineno = self.getlineno(mark)
867 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000868 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000869 text.yview(float(newtop))
870
871 def getwindowlines(self):
872 text = self.text
873 top = self.getlineno("@0,0")
874 bot = self.getlineno("@0,65535")
875 if top == bot and text.winfo_height() == 1:
876 # Geometry manager hasn't run yet
877 height = int(text['height'])
878 bot = top + height - 1
879 return top, bot
880
881 def getlineno(self, mark="insert"):
882 text = self.text
883 return int(float(text.index(mark)))
884
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000885 def get_geometry(self):
886 "Return (width, height, x, y)"
887 geom = self.top.wm_geometry()
888 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
889 tuple = (map(int, m.groups()))
890 return tuple
891
David Scherer7aced172000-08-15 01:13:23 +0000892 def close_event(self, event):
893 self.close()
894
895 def maybesave(self):
896 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000897 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000898 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000899 self.top.deiconify()
900 self.top.lower()
901 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000902 return self.io.maybesave()
903
904 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000905 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +0000906 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000907 self._close()
908 return reply
909
910 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000911 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000912 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000913 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000914 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000915 self.io.close()
916 self.io = None
917 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000918 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000919 self.color.close(False)
920 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000921 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000922 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000923 self.per.close()
924 self.per = None
925 self.top.destroy()
926 if self.close_hook:
927 # unless override: unregister from flist, terminate if last window
928 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000929
930 def load_extensions(self):
931 self.extensions = {}
932 self.load_standard_extensions()
933
934 def unload_extensions(self):
935 for ins in self.extensions.values():
936 if hasattr(ins, "close"):
937 ins.close()
938 self.extensions = {}
939
940 def load_standard_extensions(self):
941 for name in self.get_standard_extension_names():
942 try:
943 self.load_extension(name)
944 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000945 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +0000946 import traceback
947 traceback.print_exc()
948
949 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000950 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000951
952 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000953 try:
954 mod = __import__(name, globals(), locals(), [])
955 except ImportError:
956 print "\nFailed to import extension: ", name
957 return
David Scherer7aced172000-08-15 01:13:23 +0000958 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000959 keydefs = idleConf.GetExtensionBindings(name)
960 if hasattr(cls, "menudefs"):
961 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000962 ins = cls(self)
963 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000964 if keydefs:
965 self.apply_bindings(keydefs)
966 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000967 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000968 while methodname[:1] == '<':
969 methodname = methodname[1:]
970 while methodname[-1:] == '>':
971 methodname = methodname[:-1]
972 methodname = methodname + "_event"
973 if hasattr(ins, methodname):
974 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000975
976 def apply_bindings(self, keydefs=None):
977 if keydefs is None:
978 keydefs = self.Bindings.default_keydefs
979 text = self.text
980 text.keydefs = keydefs
981 for event, keylist in keydefs.items():
982 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000983 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000984
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000985 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000986 """Add appropriate entries to the menus and submenus
987
988 Menus that are absent or None in self.menudict are ignored.
989 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000990 if menudefs is None:
991 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000992 if keydefs is None:
993 keydefs = self.Bindings.default_keydefs
994 menudict = self.menudict
995 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000996 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000997 menu = menudict.get(mname)
998 if not menu:
999 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001000 for entry in entrylist:
1001 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001002 menu.add_separator()
1003 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001004 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001005 checkbutton = (label[:1] == '!')
1006 if checkbutton:
1007 label = label[1:]
1008 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001009 accelerator = get_accelerator(keydefs, eventname)
1010 def command(text=text, eventname=eventname):
1011 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001012 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001013 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001014 menu.add_checkbutton(label=label, underline=underline,
1015 command=command, accelerator=accelerator,
1016 variable=var)
1017 else:
1018 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001019 command=command,
1020 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001021
1022 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001023 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001024 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001025 value = var.get()
1026 return value
1027 else:
1028 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001029
1030 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001031 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001032 if var:
1033 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001034 else:
1035 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001036
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001037 def get_var_obj(self, name, vartype=None):
1038 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001039 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001040 # create a Tkinter variable object with self.text as master:
1041 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001042 return var
1043
1044 # Tk implementations of "virtual text methods" -- each platform
1045 # reusing IDLE's support code needs to define these for its GUI's
1046 # flavor of widget.
1047
1048 # Is character at text_index in a Python string? Return 0 for
1049 # "guaranteed no", true for anything else. This info is expensive
1050 # to compute ab initio, but is probably already known by the
1051 # platform's colorizer.
1052
1053 def is_char_in_string(self, text_index):
1054 if self.color:
1055 # Return true iff colorizer hasn't (re)gotten this far
1056 # yet, or the character is tagged as being in a string
1057 return self.text.tag_prevrange("TODO", text_index) or \
1058 "STRING" in self.text.tag_names(text_index)
1059 else:
1060 # The colorizer is missing: assume the worst
1061 return 1
1062
1063 # If a selection is defined in the text widget, return (start,
1064 # end) as Tkinter text indices, otherwise return (None, None)
1065 def get_selection_indices(self):
1066 try:
1067 first = self.text.index("sel.first")
1068 last = self.text.index("sel.last")
1069 return first, last
1070 except TclError:
1071 return None, None
1072
1073 # Return the text widget's current view of what a tab stop means
1074 # (equivalent width in spaces).
1075
1076 def get_tabwidth(self):
1077 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1078 return int(current)
1079
1080 # Set the text widget's current view of what a tab stop means.
1081
1082 def set_tabwidth(self, newtabwidth):
1083 text = self.text
1084 if self.get_tabwidth() != newtabwidth:
1085 pixels = text.tk.call("font", "measure", text["font"],
1086 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001087 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001088 text.configure(tabs=pixels)
1089
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001090 # If ispythonsource and guess are true, guess a good value for
1091 # indentwidth based on file content (if possible), and if
1092 # indentwidth != tabwidth set usetabs false.
1093 # In any case, adjust the Text widget's view of what a tab
1094 # character means.
1095
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001096 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001097 if guess and ispythonsource:
1098 i = self.guess_indent()
1099 if 2 <= i <= 8:
1100 self.indentwidth = i
1101 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001102 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001103 self.set_tabwidth(self.tabwidth)
1104
1105 def smart_backspace_event(self, event):
1106 text = self.text
1107 first, last = self.get_selection_indices()
1108 if first and last:
1109 text.delete(first, last)
1110 text.mark_set("insert", first)
1111 return "break"
1112 # Delete whitespace left, until hitting a real char or closest
1113 # preceding virtual tab stop.
1114 chars = text.get("insert linestart", "insert")
1115 if chars == '':
1116 if text.compare("insert", ">", "1.0"):
1117 # easy: delete preceding newline
1118 text.delete("insert-1c")
1119 else:
1120 text.bell() # at start of buffer
1121 return "break"
1122 if chars[-1] not in " \t":
1123 # easy: delete preceding real char
1124 text.delete("insert-1c")
1125 return "break"
1126 # Ick. It may require *inserting* spaces if we back up over a
1127 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001128 tabwidth = self.tabwidth
1129 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001130 assert have > 0
1131 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001132 # Debug prompt is multilined....
1133 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001134 ncharsdeleted = 0
1135 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001136 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001137 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001138 chars = chars[:-1]
1139 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001140 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001141 if have <= want or chars[-1] not in " \t":
1142 break
1143 text.undo_block_start()
1144 text.delete("insert-%dc" % ncharsdeleted, "insert")
1145 if have < want:
1146 text.insert("insert", ' ' * (want - have))
1147 text.undo_block_stop()
1148 return "break"
1149
1150 def smart_indent_event(self, event):
1151 # if intraline selection:
1152 # delete it
1153 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001154 # do indent-region
1155 # else:
1156 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001157 text = self.text
1158 first, last = self.get_selection_indices()
1159 text.undo_block_start()
1160 try:
1161 if first and last:
1162 if index2line(first) != index2line(last):
1163 return self.indent_region_event(event)
1164 text.delete(first, last)
1165 text.mark_set("insert", first)
1166 prefix = text.get("insert linestart", "insert")
1167 raw, effective = classifyws(prefix, self.tabwidth)
1168 if raw == len(prefix):
1169 # only whitespace to the left
1170 self.reindent_to(effective + self.indentwidth)
1171 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001172 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001173 if self.usetabs:
1174 pad = '\t'
1175 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001176 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001177 n = self.indentwidth
1178 pad = ' ' * (n - effective % n)
1179 text.insert("insert", pad)
1180 text.see("insert")
1181 return "break"
1182 finally:
1183 text.undo_block_stop()
1184
1185 def newline_and_indent_event(self, event):
1186 text = self.text
1187 first, last = self.get_selection_indices()
1188 text.undo_block_start()
1189 try:
1190 if first and last:
1191 text.delete(first, last)
1192 text.mark_set("insert", first)
1193 line = text.get("insert linestart", "insert")
1194 i, n = 0, len(line)
1195 while i < n and line[i] in " \t":
1196 i = i+1
1197 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001198 # the cursor is in or at leading indentation in a continuation
1199 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001200 text.insert("insert linestart", '\n')
1201 return "break"
1202 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001203 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001204 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001205 last_line_of_prompt = sys.ps1.split('\n')[-1]
1206 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001207 line = line[:-1]
1208 i = i+1
1209 if i:
1210 text.delete("insert - %d chars" % i, "insert")
1211 # strip whitespace after insert point
1212 while text.get("insert") in " \t":
1213 text.delete("insert")
1214 # start new line
1215 text.insert("insert", '\n')
1216
1217 # adjust indentation for continuations and block
1218 # open/close first need to find the last stmt
1219 lno = index2line(text.index('insert'))
1220 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001221 if not self.context_use_ps1:
1222 for context in self.num_context_lines:
1223 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001224 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001225 rawtext = text.get(startatindex, "insert")
1226 y.set_str(rawtext)
1227 bod = y.find_good_parse_start(
1228 self.context_use_ps1,
1229 self._build_char_in_string_func(startatindex))
1230 if bod is not None or startat == 1:
1231 break
1232 y.set_lo(bod or 0)
1233 else:
1234 r = text.tag_prevrange("console", "insert")
1235 if r:
1236 startatindex = r[1]
1237 else:
1238 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001239 rawtext = text.get(startatindex, "insert")
1240 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001241 y.set_lo(0)
1242
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001243 c = y.get_continuation_type()
1244 if c != PyParse.C_NONE:
1245 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001246 if c == PyParse.C_STRING_FIRST_LINE:
1247 # after the first line of a string; do not indent at all
1248 pass
1249 elif c == PyParse.C_STRING_NEXT_LINES:
1250 # inside a string which started before this line;
1251 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001252 text.insert("insert", indent)
1253 elif c == PyParse.C_BRACKET:
1254 # line up with the first (if any) element of the
1255 # last open bracket structure; else indent one
1256 # level beyond the indent of the line with the
1257 # last open bracket
1258 self.reindent_to(y.compute_bracket_indent())
1259 elif c == PyParse.C_BACKSLASH:
1260 # if more than one line in this stmt already, just
1261 # mimic the current indent; else if initial line
1262 # has a start on an assignment stmt, indent to
1263 # beyond leftmost =; else to beyond first chunk of
1264 # non-whitespace on initial line
1265 if y.get_num_lines_in_stmt() > 1:
1266 text.insert("insert", indent)
1267 else:
1268 self.reindent_to(y.compute_backslash_indent())
1269 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001270 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001271 return "break"
1272
1273 # This line starts a brand new stmt; indent relative to
1274 # indentation of initial line of closest preceding
1275 # interesting stmt.
1276 indent = y.get_base_indent_string()
1277 text.insert("insert", indent)
1278 if y.is_block_opener():
1279 self.smart_indent_event(event)
1280 elif indent and y.is_block_closer():
1281 self.smart_backspace_event(event)
1282 return "break"
1283 finally:
1284 text.see("insert")
1285 text.undo_block_stop()
1286
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001287 # Our editwin provides a is_char_in_string function that works
1288 # with a Tk text index, but PyParse only knows about offsets into
1289 # a string. This builds a function for PyParse that accepts an
1290 # offset.
1291
1292 def _build_char_in_string_func(self, startindex):
1293 def inner(offset, _startindex=startindex,
1294 _icis=self.is_char_in_string):
1295 return _icis(_startindex + "+%dc" % offset)
1296 return inner
1297
1298 def indent_region_event(self, event):
1299 head, tail, chars, lines = self.get_region()
1300 for pos in range(len(lines)):
1301 line = lines[pos]
1302 if line:
1303 raw, effective = classifyws(line, self.tabwidth)
1304 effective = effective + self.indentwidth
1305 lines[pos] = self._make_blanks(effective) + line[raw:]
1306 self.set_region(head, tail, chars, lines)
1307 return "break"
1308
1309 def dedent_region_event(self, event):
1310 head, tail, chars, lines = self.get_region()
1311 for pos in range(len(lines)):
1312 line = lines[pos]
1313 if line:
1314 raw, effective = classifyws(line, self.tabwidth)
1315 effective = max(effective - self.indentwidth, 0)
1316 lines[pos] = self._make_blanks(effective) + line[raw:]
1317 self.set_region(head, tail, chars, lines)
1318 return "break"
1319
1320 def comment_region_event(self, event):
1321 head, tail, chars, lines = self.get_region()
1322 for pos in range(len(lines) - 1):
1323 line = lines[pos]
1324 lines[pos] = '##' + line
1325 self.set_region(head, tail, chars, lines)
1326
1327 def uncomment_region_event(self, event):
1328 head, tail, chars, lines = self.get_region()
1329 for pos in range(len(lines)):
1330 line = lines[pos]
1331 if not line:
1332 continue
1333 if line[:2] == '##':
1334 line = line[2:]
1335 elif line[:1] == '#':
1336 line = line[1:]
1337 lines[pos] = line
1338 self.set_region(head, tail, chars, lines)
1339
1340 def tabify_region_event(self, event):
1341 head, tail, chars, lines = self.get_region()
1342 tabwidth = self._asktabwidth()
1343 for pos in range(len(lines)):
1344 line = lines[pos]
1345 if line:
1346 raw, effective = classifyws(line, tabwidth)
1347 ntabs, nspaces = divmod(effective, tabwidth)
1348 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1349 self.set_region(head, tail, chars, lines)
1350
1351 def untabify_region_event(self, event):
1352 head, tail, chars, lines = self.get_region()
1353 tabwidth = self._asktabwidth()
1354 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001355 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001356 self.set_region(head, tail, chars, lines)
1357
1358 def toggle_tabs_event(self, event):
1359 if self.askyesno(
1360 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001361 "Turn tabs " + ("on", "off")[self.usetabs] +
1362 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001363 ("will be", "remains at")[self.usetabs] + " 8." +
1364 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001365 parent=self.text):
1366 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001367 # Try to prevent inconsistent indentation.
1368 # User must change indent width manually after using tabs.
1369 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001370 return "break"
1371
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001372 # XXX this isn't bound to anything -- see tabwidth comments
1373## def change_tabwidth_event(self, event):
1374## new = self._asktabwidth()
1375## if new != self.tabwidth:
1376## self.tabwidth = new
1377## self.set_indentation_params(0, guess=0)
1378## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001379
1380 def change_indentwidth_event(self, event):
1381 new = self.askinteger(
1382 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001383 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001384 parent=self.text,
1385 initialvalue=self.indentwidth,
1386 minvalue=2,
1387 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001388 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001389 self.indentwidth = new
1390 return "break"
1391
1392 def get_region(self):
1393 text = self.text
1394 first, last = self.get_selection_indices()
1395 if first and last:
1396 head = text.index(first + " linestart")
1397 tail = text.index(last + "-1c lineend +1c")
1398 else:
1399 head = text.index("insert linestart")
1400 tail = text.index("insert lineend +1c")
1401 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001402 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001403 return head, tail, chars, lines
1404
1405 def set_region(self, head, tail, chars, lines):
1406 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001407 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001408 if newchars == chars:
1409 text.bell()
1410 return
1411 text.tag_remove("sel", "1.0", "end")
1412 text.mark_set("insert", head)
1413 text.undo_block_start()
1414 text.delete(head, tail)
1415 text.insert(head, newchars)
1416 text.undo_block_stop()
1417 text.tag_add("sel", head, "insert")
1418
1419 # Make string that displays as n leading blanks.
1420
1421 def _make_blanks(self, n):
1422 if self.usetabs:
1423 ntabs, nspaces = divmod(n, self.tabwidth)
1424 return '\t' * ntabs + ' ' * nspaces
1425 else:
1426 return ' ' * n
1427
1428 # Delete from beginning of line to insert point, then reinsert
1429 # column logical (meaning use tabs if appropriate) spaces.
1430
1431 def reindent_to(self, column):
1432 text = self.text
1433 text.undo_block_start()
1434 if text.compare("insert linestart", "!=", "insert"):
1435 text.delete("insert linestart", "insert")
1436 if column:
1437 text.insert("insert", self._make_blanks(column))
1438 text.undo_block_stop()
1439
1440 def _asktabwidth(self):
1441 return self.askinteger(
1442 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001443 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001444 parent=self.text,
1445 initialvalue=self.indentwidth,
1446 minvalue=2,
1447 maxvalue=16) or self.tabwidth
1448
1449 # Guess indentwidth from text content.
1450 # Return guessed indentwidth. This should not be believed unless
1451 # it's in a reasonable range (e.g., it will be 0 if no indented
1452 # blocks are found).
1453
1454 def guess_indent(self):
1455 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1456 if opener and indented:
1457 raw, indentsmall = classifyws(opener, self.tabwidth)
1458 raw, indentlarge = classifyws(indented, self.tabwidth)
1459 else:
1460 indentsmall = indentlarge = 0
1461 return indentlarge - indentsmall
1462
1463# "line.col" -> line, as an int
1464def index2line(index):
1465 return int(float(index))
1466
1467# Look at the leading whitespace in s.
1468# Return pair (# of leading ws characters,
1469# effective # of leading blanks after expanding
1470# tabs to width tabwidth)
1471
1472def classifyws(s, tabwidth):
1473 raw = effective = 0
1474 for ch in s:
1475 if ch == ' ':
1476 raw = raw + 1
1477 effective = effective + 1
1478 elif ch == '\t':
1479 raw = raw + 1
1480 effective = (effective // tabwidth + 1) * tabwidth
1481 else:
1482 break
1483 return raw, effective
1484
1485import tokenize
1486_tokenize = tokenize
1487del tokenize
1488
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001489class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001490
1491 # .run() chews over the Text widget, looking for a block opener
1492 # and the stmt following it. Returns a pair,
1493 # (line containing block opener, line containing stmt)
1494 # Either or both may be None.
1495
1496 def __init__(self, text, tabwidth):
1497 self.text = text
1498 self.tabwidth = tabwidth
1499 self.i = self.finished = 0
1500 self.blkopenline = self.indentedline = None
1501
1502 def readline(self):
1503 if self.finished:
1504 return ""
1505 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001506 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001507 if self.text.compare(mark, ">=", "end"):
1508 return ""
1509 return self.text.get(mark, mark + " lineend+1c")
1510
1511 def tokeneater(self, type, token, start, end, line,
1512 INDENT=_tokenize.INDENT,
1513 NAME=_tokenize.NAME,
1514 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1515 if self.finished:
1516 pass
1517 elif type == NAME and token in OPENERS:
1518 self.blkopenline = line
1519 elif type == INDENT and self.blkopenline:
1520 self.indentedline = line
1521 self.finished = 1
1522
1523 def run(self):
1524 save_tabsize = _tokenize.tabsize
1525 _tokenize.tabsize = self.tabwidth
1526 try:
1527 try:
1528 _tokenize.tokenize(self.readline, self.tokeneater)
1529 except _tokenize.TokenError:
1530 # since we cut off the tokenizer early, we can trigger
1531 # spurious errors
1532 pass
1533 finally:
1534 _tokenize.tabsize = save_tabsize
1535 return self.blkopenline, self.indentedline
1536
1537### end autoindent code ###
1538
David Scherer7aced172000-08-15 01:13:23 +00001539def prepstr(s):
1540 # Helper to extract the underscore from a string, e.g.
1541 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001542 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001543 if i >= 0:
1544 s = s[:i] + s[i+1:]
1545 return i, s
1546
1547
1548keynames = {
1549 'bracketleft': '[',
1550 'bracketright': ']',
1551 'slash': '/',
1552}
1553
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001554def get_accelerator(keydefs, eventname):
1555 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001556 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1557 # if not keylist:
1558 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1559 "<<open-module>>",
1560 "<<goto-line>>",
1561 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001562 return ""
1563 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001564 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001565 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1566 s = re.sub("Key-", "", s)
1567 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1568 s = re.sub("Control-", "Ctrl-", s)
1569 s = re.sub("-", "+", s)
1570 s = re.sub("><", " ", s)
1571 s = re.sub("<", "", s)
1572 s = re.sub(">", "", s)
1573 return s
1574
1575
1576def fixwordbreaks(root):
1577 # Make sure that Tk's double-click and next/previous word
1578 # operations use our definition of a word (i.e. an identifier)
1579 tk = root.tk
1580 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1581 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1582 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1583
1584
1585def test():
1586 root = Tk()
1587 fixwordbreaks(root)
1588 root.withdraw()
1589 if sys.argv[1:]:
1590 filename = sys.argv[1]
1591 else:
1592 filename = None
1593 edit = EditorWindow(root=root, filename=filename)
1594 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001595 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001596 root.mainloop()
1597 root.destroy()
1598
1599if __name__ == '__main__':
1600 test()