blob: 935a39f6c29da393a9a2b0e115b76e08f5ad710c [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__
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000051 return file, filename, descr
52
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000053class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +000054 from idlelib.Percolator import Percolator
55 from idlelib.ColorDelegator import ColorDelegator
56 from idlelib.UndoDelegator import UndoDelegator
57 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
58 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +000059 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +000060 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000061
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000062 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000063
64 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000065 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000066 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000067 if sys.platform.count('linux'):
68 # look for html docs in a couple of standard places
69 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
70 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
71 dochome = '/var/www/html/python/index.html'
72 else:
73 basepath = '/usr/share/doc/' # standard location
74 dochome = os.path.join(basepath, pyver,
75 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000076 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000077 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000078 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000079 if os.path.isfile(chmfile):
80 dochome = chmfile
Ronald Oussoren19302d92006-06-11 14:33:36 +000081 elif macosxSupport.runningAsOSXApp():
82 # documentation is stored inside the python framework
83 dochome = os.path.join(sys.prefix,
84 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000085 dochome = os.path.normpath(dochome)
86 if os.path.isfile(dochome):
87 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +000088 if sys.platform == 'darwin':
89 # Safari requires real file:-URLs
90 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000091 else:
Raymond Hettinger7b4c2be2009-01-20 10:46:23 +000092 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +000093 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000094 self.flist = flist
95 root = root or flist.root
96 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +000097 try:
98 sys.ps1
99 except AttributeError:
100 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000101 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000102 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000103 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000104 self.tkinter_vars = flist.vars
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200105 #self.top.instance_dict makes flist.inversedict available to
106 #configDialog.py so it can access all EditorWindow instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000107 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000108 else:
109 self.tkinter_vars = {} # keys: Tkinter event names
110 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000111 self.top.instance_dict = {}
112 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000113 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000114 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000115 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000116 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000117 text_options = {
118 'name': 'text',
119 'padx': 5,
120 'wrap': 'none',
121 'width': self.width,
122 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
123 if TkVersion >= 8.5:
124 # Starting with tk 8.5 we have to set the new tabstyle option
125 # to 'wordprocessor' to achieve the same display of tabs as in
126 # older tk versions.
127 text_options['tabstyle'] = 'wordprocessor'
128 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000129 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000130
131 self.createmenubar()
132 self.apply_bindings()
133
134 self.top.protocol("WM_DELETE_WINDOW", self.close)
135 self.top.bind("<<close-window>>", self.close_event)
Ronald Oussoren17db4952006-07-23 09:41:09 +0000136 if macosxSupport.runningAsOSXApp():
137 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000138 text.bind('<<close-window>>', self.close_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000139 # Some OS X systems have only one mouse button,
140 # so use control-click for pulldown menus there.
141 # (Note, AquaTk defines <2> as the right button if
142 # present and the Tk Text widget already binds <2>.)
143 text.bind("<Control-Button-1>",self.right_menu_event)
144 else:
145 # Elsewhere, use right-click for pulldown menus.
146 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000147 text.bind("<<cut>>", self.cut)
148 text.bind("<<copy>>", self.copy)
149 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000150 text.bind("<<center-insert>>", self.center_insert_event)
151 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000152 text.bind("<<python-docs>>", self.python_docs)
153 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000154 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000155 text.bind("<<open-module>>", self.open_module)
156 text.bind("<<do-nothing>>", lambda event: "break")
157 text.bind("<<select-all>>", self.select_all)
158 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000159 text.bind("<<find>>", self.find_event)
160 text.bind("<<find-again>>", self.find_again_event)
161 text.bind("<<find-in-files>>", self.find_in_files_event)
162 text.bind("<<find-selection>>", self.find_selection_event)
163 text.bind("<<replace>>", self.replace_event)
164 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000165 text.bind("<<smart-backspace>>",self.smart_backspace_event)
166 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
167 text.bind("<<smart-indent>>",self.smart_indent_event)
168 text.bind("<<indent-region>>",self.indent_region_event)
169 text.bind("<<dedent-region>>",self.dedent_region_event)
170 text.bind("<<comment-region>>",self.comment_region_event)
171 text.bind("<<uncomment-region>>",self.uncomment_region_event)
172 text.bind("<<tabify-region>>",self.tabify_region_event)
173 text.bind("<<untabify-region>>",self.untabify_region_event)
174 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
175 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000176 text.bind("<Left>", self.move_at_edge_if_selection(0))
177 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000178 text.bind("<<del-word-left>>", self.del_word_left)
179 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000180 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000181
David Scherer7aced172000-08-15 01:13:23 +0000182 if flist:
183 flist.inversedict[self] = key
184 if key:
185 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000186 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000187 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
188 text.bind("<<open-class-browser>>", self.open_class_browser)
189 text.bind("<<open-path-browser>>", self.open_path_browser)
190
Steven M. Gava898a3652001-10-07 11:10:44 +0000191 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000192 vbar['command'] = text.yview
193 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000194 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000195 fontWeight = 'normal'
196 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000197 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000198 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
199 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
200 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000201 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
202 text.pack(side=TOP, fill=BOTH, expand=1)
203 text.focus_set()
204
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000205 # usetabs true -> literal tab characters are used by indent and
206 # dedent cmds, possibly mixed with spaces if
207 # indentwidth is not a multiple of tabwidth,
208 # which will cause Tabnanny to nag!
209 # false -> tab characters are converted to spaces by indent
210 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000211 # Although use-spaces=0 can be configured manually in config-main.def,
212 # configuration of tabs v. spaces is not supported in the configuration
213 # dialog. IDLE promotes the preferred Python indentation: use spaces!
214 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
215 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000216
217 # tabwidth is the display width of a literal tab character.
218 # CAUTION: telling Tk to use anything other than its default
219 # tab setting causes it to use an entirely different tabbing algorithm,
220 # treating tab stops as fixed distances from the left margin.
221 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000222 self.tabwidth = 8 # must remain 8 until Tk is fixed.
223
224 # indentwidth is the number of screen characters per indent level.
225 # The recommended Python indentation is four spaces.
226 self.indentwidth = self.tabwidth
227 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000228
229 # If context_use_ps1 is true, parsing searches back for a ps1 line;
230 # else searches for a popular (if, def, ...) Python stmt.
231 self.context_use_ps1 = False
232
233 # When searching backwards for a reliable place to begin parsing,
234 # first start num_context_lines[0] lines back, then
235 # num_context_lines[1] lines back if that didn't work, and so on.
236 # The last value should be huge (larger than the # of lines in a
237 # conceivable file).
238 # Making the initial values larger slows things down more often.
239 self.num_context_lines = 50, 500, 5000000
240
David Scherer7aced172000-08-15 01:13:23 +0000241 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000242
243 self.undo = undo = self.UndoDelegator()
244 per.insertfilter(undo)
245 text.undo_block_start = undo.undo_block_start
246 text.undo_block_stop = undo.undo_block_stop
247 undo.set_saved_change_hook(self.saved_change_hook)
248
249 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000250 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000251 io.set_filename_change_hook(self.filename_change_hook)
252
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000253 # Create the recent files submenu
254 self.recent_files_menu = Menu(self.menubar)
255 self.menudict['file'].insert_cascade(3, label='Recent Files',
256 underline=0,
257 menu=self.recent_files_menu)
258 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000259
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000260 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000261 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000262 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000263 io.loadfile(filename)
264 else:
265 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000266 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000267 self.saved_change_hook()
268
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000269 self.set_indentation_params(self.ispythonsource(filename))
270
David Scherer7aced172000-08-15 01:13:23 +0000271 self.load_extensions()
272
273 menu = self.menudict.get('windows')
274 if menu:
275 end = menu.index("end")
276 if end is None:
277 end = -1
278 if end >= 0:
279 menu.add_separator()
280 end = end + 1
281 self.wmenu_end = end
282 WindowList.register_callback(self.postwindowsmenu)
283
284 # Some abstractions so IDLE extensions are cross-IDE
285 self.askyesno = tkMessageBox.askyesno
286 self.askinteger = tkSimpleDialog.askinteger
287 self.showerror = tkMessageBox.showerror
288
Martin v. Löwis307021f2005-11-27 16:59:04 +0000289 def _filename_to_unicode(self, filename):
290 """convert filename to unicode in order to display it in Tk"""
291 if isinstance(filename, unicode) or not filename:
292 return filename
293 else:
294 try:
295 return filename.decode(self.filesystemencoding)
296 except UnicodeDecodeError:
297 # XXX
298 try:
299 return filename.decode(self.encoding)
300 except UnicodeDecodeError:
301 # byte-to-byte conversion
302 return filename.decode('iso8859-1')
303
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000304 def new_callback(self, event):
305 dirname, basename = self.io.defaultfilename()
306 self.flist.new(dirname)
307 return "break"
308
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000309 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400310 if (event.state & 4) != 0 and event.keysym == "Home":
311 # state&4==Control. If <Control-Home>, use the Tk binding.
312 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000313 if self.text.index("iomark") and \
314 self.text.compare("iomark", "<=", "insert lineend") and \
315 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400316 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000317 insertpt = int(self.text.index("iomark").split(".")[1])
318 else:
319 line = self.text.get("insert linestart", "insert lineend")
320 for insertpt in xrange(len(line)):
321 if line[insertpt] not in (' ','\t'):
322 break
323 else:
324 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000325 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000326 if insertpt == lineat:
327 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000328 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000329 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400330 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000331 self.text.tag_remove("sel", "1.0", "end")
332 else:
333 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400334 self.text.mark_set("my_anchor", "insert") # there was no previous selection
335 else:
336 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
337 self.text.mark_set("my_anchor", "sel.first") # extend back
338 else:
339 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000340 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400341 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000342 if self.text.compare(first,">",last):
343 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000344 self.text.tag_remove("sel", "1.0", "end")
345 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000346 self.text.mark_set("insert", dest)
347 self.text.see("insert")
348 return "break"
349
David Scherer7aced172000-08-15 01:13:23 +0000350 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000351 self.status_bar = self.MultiStatusBar(self.top)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000352 if macosxSupport.runningAsOSXApp():
353 # Insert some padding to avoid obscuring some of the statusbar
354 # by the resize widget.
355 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000356 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
357 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
358 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000359 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
360 self.text.event_add("<<set-line-and-column>>",
361 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000362 self.text.after_idle(self.set_line_and_column)
363
364 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000365 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000366 self.status_bar.set_label('column', 'Col: %s' % column)
367 self.status_bar.set_label('line', 'Ln: %s' % line)
368
David Scherer7aced172000-08-15 01:13:23 +0000369 menu_specs = [
370 ("file", "_File"),
371 ("edit", "_Edit"),
372 ("format", "F_ormat"),
373 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000374 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000375 ("windows", "_Windows"),
376 ("help", "_Help"),
377 ]
378
Ronald Oussoren19302d92006-06-11 14:33:36 +0000379 if macosxSupport.runningAsOSXApp():
380 del menu_specs[-3]
381 menu_specs[-2] = ("windows", "_Window")
382
383
David Scherer7aced172000-08-15 01:13:23 +0000384 def createmenubar(self):
385 mbar = self.menubar
386 self.menudict = menudict = {}
387 for name, label in self.menu_specs:
388 underline, label = prepstr(label)
389 menudict[name] = menu = Menu(mbar, name=name)
390 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000391
Ned Deily4a705502011-01-18 04:33:22 +0000392 if macosxSupport.isCarbonAquaTk(self.root):
Ronald Oussoren19302d92006-06-11 14:33:36 +0000393 # Insert the application menu
394 menudict['application'] = menu = Menu(mbar, name='apple')
395 mbar.add_cascade(label='IDLE', menu=menu)
396
David Scherer7aced172000-08-15 01:13:23 +0000397 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000398 self.base_helpmenu_length = self.menudict['help'].index(END)
399 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000400
401 def postwindowsmenu(self):
402 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000403 menu = self.menudict['windows']
404 end = menu.index("end")
405 if end is None:
406 end = -1
407 if end > self.wmenu_end:
408 menu.delete(self.wmenu_end+1, end)
409 WindowList.add_windows_to_menu(menu)
410
411 rmenu = None
412
413 def right_menu_event(self, event):
414 self.text.tag_remove("sel", "1.0", "end")
415 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
416 if not self.rmenu:
417 self.make_rmenu()
418 rmenu = self.rmenu
419 self.event = event
420 iswin = sys.platform[:3] == 'win'
421 if iswin:
422 self.text.config(cursor="arrow")
423 rmenu.tk_popup(event.x_root, event.y_root)
424 if iswin:
425 self.text.config(cursor="ibeam")
426
427 rmenu_specs = [
428 # ("Label", "<<virtual-event>>"), ...
429 ("Close", "<<close-window>>"), # Example
430 ]
431
432 def make_rmenu(self):
433 rmenu = Menu(self.text, tearoff=0)
434 for label, eventname in self.rmenu_specs:
435 def command(text=self.text, eventname=eventname):
436 text.event_generate(eventname)
437 rmenu.add_command(label=label, command=command)
438 self.rmenu = rmenu
439
440 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000441 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000442
Steven M. Gava3b55a892001-11-21 05:56:26 +0000443 def config_dialog(self, event=None):
444 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000445
David Scherer7aced172000-08-15 01:13:23 +0000446 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000447 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiserd5f49102007-10-04 02:53:07 +0000448 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000449
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000450 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000451 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000452 try:
453 os.startfile(self.help_url)
454 except WindowsError as why:
455 tkMessageBox.showerror(title='Document Start Failure',
456 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000457 else:
458 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000459 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000460
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000461 def cut(self,event):
462 self.text.event_generate("<<Cut>>")
463 return "break"
464
465 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000466 if not self.text.tag_ranges("sel"):
467 # There is no selection, so do nothing and maybe interrupt.
468 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000469 self.text.event_generate("<<Copy>>")
470 return "break"
471
472 def paste(self,event):
473 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000474 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000475 return "break"
476
David Scherer7aced172000-08-15 01:13:23 +0000477 def select_all(self, event=None):
478 self.text.tag_add("sel", "1.0", "end-1c")
479 self.text.mark_set("insert", "1.0")
480 self.text.see("insert")
481 return "break"
482
483 def remove_selection(self, event=None):
484 self.text.tag_remove("sel", "1.0", "end")
485 self.text.see("insert")
486
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000487 def move_at_edge_if_selection(self, edge_index):
488 """Cursor move begins at start or end of selection
489
490 When a left/right cursor key is pressed create and return to Tkinter a
491 function which causes a cursor move from the associated edge of the
492 selection.
493
494 """
495 self_text_index = self.text.index
496 self_text_mark_set = self.text.mark_set
497 edges_table = ("sel.first+1c", "sel.last-1c")
498 def move_at_edge(event):
499 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
500 try:
501 self_text_index("sel.first")
502 self_text_mark_set("insert", edges_table[edge_index])
503 except TclError:
504 pass
505 return move_at_edge
506
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000507 def del_word_left(self, event):
508 self.text.event_generate('<Meta-Delete>')
509 return "break"
510
511 def del_word_right(self, event):
512 self.text.event_generate('<Meta-d>')
513 return "break"
514
Steven M. Gavac5976402002-01-04 03:06:08 +0000515 def find_event(self, event):
516 SearchDialog.find(self.text)
517 return "break"
518
519 def find_again_event(self, event):
520 SearchDialog.find_again(self.text)
521 return "break"
522
523 def find_selection_event(self, event):
524 SearchDialog.find_selection(self.text)
525 return "break"
526
527 def find_in_files_event(self, event):
528 GrepDialog.grep(self.text, self.io, self.flist)
529 return "break"
530
531 def replace_event(self, event):
532 ReplaceDialog.replace(self.text)
533 return "break"
534
535 def goto_line_event(self, event):
536 text = self.text
537 lineno = tkSimpleDialog.askinteger("Goto",
538 "Go to line number:",parent=text)
539 if lineno is None:
540 return "break"
541 if lineno <= 0:
542 text.bell()
543 return "break"
544 text.mark_set("insert", "%d.0" % lineno)
545 text.see("insert")
546
David Scherer7aced172000-08-15 01:13:23 +0000547 def open_module(self, event=None):
548 # XXX Shouldn't this be in IOBinding or in FileList?
549 try:
550 name = self.text.get("sel.first", "sel.last")
551 except TclError:
552 name = ""
553 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000554 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000555 name = tkSimpleDialog.askstring("Module",
556 "Enter the name of a Python module\n"
557 "to search on sys.path and open:",
558 parent=self.text, initialvalue=name)
559 if name:
560 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000561 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000562 return
David Scherer7aced172000-08-15 01:13:23 +0000563 # XXX Ought to insert current file's directory in front of path
564 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000565 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000566 except (NameError, ImportError), msg:
567 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
568 return
569 if type != imp.PY_SOURCE:
570 tkMessageBox.showerror("Unsupported type",
571 "%s is not a source module" % name, parent=self.text)
572 return
573 if f:
574 f.close()
575 if self.flist:
576 self.flist.open(file)
577 else:
578 self.io.loadfile(file)
579
580 def open_class_browser(self, event=None):
581 filename = self.io.filename
582 if not filename:
583 tkMessageBox.showerror(
584 "No filename",
585 "This buffer has no associated filename",
586 master=self.text)
587 self.text.focus_set()
588 return None
589 head, tail = os.path.split(filename)
590 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000591 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000592 ClassBrowser.ClassBrowser(self.flist, base, [head])
593
594 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000595 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000596 PathBrowser.PathBrowser(self.flist)
597
598 def gotoline(self, lineno):
599 if lineno is not None and lineno > 0:
600 self.text.mark_set("insert", "%d.0" % lineno)
601 self.text.tag_remove("sel", "1.0", "end")
602 self.text.tag_add("sel", "insert", "insert +1l")
603 self.center()
604
605 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000606 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000607 return True
David Scherer7aced172000-08-15 01:13:23 +0000608 base, ext = os.path.splitext(os.path.basename(filename))
609 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000610 return True
David Scherer7aced172000-08-15 01:13:23 +0000611 try:
612 f = open(filename)
613 line = f.readline()
614 f.close()
615 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000616 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000617 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000618
619 def close_hook(self):
620 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000621 self.flist.unregister_maybe_terminate(self)
622 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000623
624 def set_close_hook(self, close_hook):
625 self.close_hook = close_hook
626
627 def filename_change_hook(self):
628 if self.flist:
629 self.flist.filename_changed_edit(self)
630 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000631 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000632 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000633
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000634 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000635 if self.color:
636 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000637 if self.ispythonsource(self.io.filename):
638 self.color = self.ColorDelegator()
639 # can add more colorizers here...
640 if self.color:
641 self.per.removefilter(self.undo)
642 self.per.insertfilter(self.color)
643 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000644
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000645 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000646 if not self.color:
647 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000648 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000649 self.per.removefilter(self.color)
650 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000651
Steven M. Gavab77d3432002-03-02 07:16:21 +0000652 def ResetColorizer(self):
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000653 "Update the colour theme"
654 # Called from self.filename_change_hook and from configDialog.py
655 self._rmcolorizer()
656 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000657 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000658 normal_colors = idleConf.GetHighlight(theme, 'normal')
659 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
660 select_colors = idleConf.GetHighlight(theme, 'hilite')
661 self.text.config(
662 foreground=normal_colors['foreground'],
663 background=normal_colors['background'],
664 insertbackground=cursor_color,
665 selectforeground=select_colors['foreground'],
666 selectbackground=select_colors['background'],
667 )
David Scherer7aced172000-08-15 01:13:23 +0000668
Steven M. Gavab1585412002-03-12 00:21:56 +0000669 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000670 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000671 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000672 fontWeight='normal'
673 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
674 fontWeight='bold'
675 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
676 idleConf.GetOption('main','EditorWindow','font-size'),
677 fontWeight))
678
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000679 def RemoveKeybindings(self):
680 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000681 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000682 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000683 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000684 self.text.event_delete(event, *keylist)
685 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000686 xkeydefs = idleConf.GetExtensionBindings(extensionName)
687 if xkeydefs:
688 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000689 self.text.event_delete(event, *keylist)
690
691 def ApplyKeybindings(self):
692 "Update the keybindings after they are changed"
693 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000694 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000695 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000696 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000697 xkeydefs = idleConf.GetExtensionBindings(extensionName)
698 if xkeydefs:
699 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000700 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000701 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000702 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000703 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000704 for item in menu[1]:
705 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000706 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000707 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000708 menu = self.menudict[menubarItem]
709 end = menu.index(END) + 1
710 for index in range(0, end):
711 if menu.type(index) == 'command':
712 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000713 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000714 itemName = menu.entrycget(index, 'label')
715 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000716 if menubarItem in menuEventDict:
717 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000718 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000719 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000720 accel = get_accelerator(keydefs, event)
721 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000722
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000723 def set_notabs_indentwidth(self):
724 "Update the indentwidth if changed and not using tabs in this window"
725 # Called from configDialog.py
726 if not self.usetabs:
727 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
728 type='int')
729
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000730 def reset_help_menu_entries(self):
731 "Update the additional help entries on the Help menu"
732 help_list = idleConf.GetAllExtraHelpSourcesList()
733 helpmenu = self.menudict['help']
734 # first delete the extra help entries, if any
735 helpmenu_length = helpmenu.index(END)
736 if helpmenu_length > self.base_helpmenu_length:
737 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
738 # then rebuild them
739 if help_list:
740 helpmenu.add_separator()
741 for entry in help_list:
742 cmd = self.__extra_help_callback(entry[1])
743 helpmenu.add_command(label=entry[0], command=cmd)
744 # and update the menu dictionary
745 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000746
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000747 def __extra_help_callback(self, helpfile):
748 "Create a callback with the helpfile value frozen at definition time"
749 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000750 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000751 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000752 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000753 try:
754 os.startfile(helpfile)
755 except WindowsError as why:
756 tkMessageBox.showerror(title='Document Start Failure',
757 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000758 else:
759 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000760 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000761
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000762 def update_recent_files_list(self, new_file=None):
763 "Load and update the recent files list and menus"
764 rf_list = []
765 if os.path.exists(self.recent_files_path):
766 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000767 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000768 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000769 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000770 rf_list_file.close()
771 if new_file:
772 new_file = os.path.abspath(new_file) + '\n'
773 if new_file in rf_list:
774 rf_list.remove(new_file) # move to top
775 rf_list.insert(0, new_file)
776 # clean and save the recent files list
777 bad_paths = []
778 for path in rf_list:
779 if '\0' in path or not os.path.exists(path[0:-1]):
780 bad_paths.append(path)
781 rf_list = [path for path in rf_list if path not in bad_paths]
782 ulchars = "1234567890ABCDEFGHIJK"
783 rf_list = rf_list[0:len(ulchars)]
784 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000785 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000786 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000787 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000788 rf_file.close()
789 # for each edit window instance, construct the recent files menu
790 for instance in self.top.instance_dict.keys():
791 menu = instance.recent_files_menu
792 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000793 for i, file_name in enumerate(rf_list):
794 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000795 # make unicode string to display non-ASCII chars correctly
796 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000797 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000798 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000799 command=callback,
800 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000801
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000802 def __recent_file_callback(self, file_name):
803 def open_recent_file(fn_closure=file_name):
804 self.io.open(editFile=fn_closure)
805 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000806
David Scherer7aced172000-08-15 01:13:23 +0000807 def saved_change_hook(self):
808 short = self.short_title()
809 long = self.long_title()
810 if short and long:
811 title = short + " - " + long
812 elif short:
813 title = short
814 elif long:
815 title = long
816 else:
817 title = "Untitled"
818 icon = short or long or title
819 if not self.get_saved():
820 title = "*%s*" % title
821 icon = "*%s" % icon
822 self.top.wm_title(title)
823 self.top.wm_iconname(icon)
824
825 def get_saved(self):
826 return self.undo.get_saved()
827
828 def set_saved(self, flag):
829 self.undo.set_saved(flag)
830
831 def reset_undo(self):
832 self.undo.reset_undo()
833
834 def short_title(self):
835 filename = self.io.filename
836 if filename:
837 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000838 # return unicode string to display non-ASCII chars correctly
839 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000840
841 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000842 # return unicode string to display non-ASCII chars correctly
843 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000844
845 def center_insert_event(self, event):
846 self.center()
847
848 def center(self, mark="insert"):
849 text = self.text
850 top, bot = self.getwindowlines()
851 lineno = self.getlineno(mark)
852 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000853 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000854 text.yview(float(newtop))
855
856 def getwindowlines(self):
857 text = self.text
858 top = self.getlineno("@0,0")
859 bot = self.getlineno("@0,65535")
860 if top == bot and text.winfo_height() == 1:
861 # Geometry manager hasn't run yet
862 height = int(text['height'])
863 bot = top + height - 1
864 return top, bot
865
866 def getlineno(self, mark="insert"):
867 text = self.text
868 return int(float(text.index(mark)))
869
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000870 def get_geometry(self):
871 "Return (width, height, x, y)"
872 geom = self.top.wm_geometry()
873 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
874 tuple = (map(int, m.groups()))
875 return tuple
876
David Scherer7aced172000-08-15 01:13:23 +0000877 def close_event(self, event):
878 self.close()
879
880 def maybesave(self):
881 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000882 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000883 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000884 self.top.deiconify()
885 self.top.lower()
886 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000887 return self.io.maybesave()
888
889 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000890 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +0000891 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000892 self._close()
893 return reply
894
895 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000896 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000897 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000898 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000899 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000900 self.io.close()
901 self.io = None
902 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000903 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000904 self.color.close(False)
905 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000906 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000907 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000908 self.per.close()
909 self.per = None
910 self.top.destroy()
911 if self.close_hook:
912 # unless override: unregister from flist, terminate if last window
913 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000914
915 def load_extensions(self):
916 self.extensions = {}
917 self.load_standard_extensions()
918
919 def unload_extensions(self):
920 for ins in self.extensions.values():
921 if hasattr(ins, "close"):
922 ins.close()
923 self.extensions = {}
924
925 def load_standard_extensions(self):
926 for name in self.get_standard_extension_names():
927 try:
928 self.load_extension(name)
929 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000930 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +0000931 import traceback
932 traceback.print_exc()
933
934 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000935 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000936
937 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000938 try:
939 mod = __import__(name, globals(), locals(), [])
940 except ImportError:
941 print "\nFailed to import extension: ", name
942 return
David Scherer7aced172000-08-15 01:13:23 +0000943 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000944 keydefs = idleConf.GetExtensionBindings(name)
945 if hasattr(cls, "menudefs"):
946 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000947 ins = cls(self)
948 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000949 if keydefs:
950 self.apply_bindings(keydefs)
951 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000952 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000953 while methodname[:1] == '<':
954 methodname = methodname[1:]
955 while methodname[-1:] == '>':
956 methodname = methodname[:-1]
957 methodname = methodname + "_event"
958 if hasattr(ins, methodname):
959 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000960
961 def apply_bindings(self, keydefs=None):
962 if keydefs is None:
963 keydefs = self.Bindings.default_keydefs
964 text = self.text
965 text.keydefs = keydefs
966 for event, keylist in keydefs.items():
967 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000968 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000969
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000970 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000971 """Add appropriate entries to the menus and submenus
972
973 Menus that are absent or None in self.menudict are ignored.
974 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000975 if menudefs is None:
976 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000977 if keydefs is None:
978 keydefs = self.Bindings.default_keydefs
979 menudict = self.menudict
980 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000981 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000982 menu = menudict.get(mname)
983 if not menu:
984 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000985 for entry in entrylist:
986 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000987 menu.add_separator()
988 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000989 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000990 checkbutton = (label[:1] == '!')
991 if checkbutton:
992 label = label[1:]
993 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000994 accelerator = get_accelerator(keydefs, eventname)
995 def command(text=text, eventname=eventname):
996 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000997 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000998 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000999 menu.add_checkbutton(label=label, underline=underline,
1000 command=command, accelerator=accelerator,
1001 variable=var)
1002 else:
1003 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001004 command=command,
1005 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001006
1007 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001008 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001009 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001010 value = var.get()
1011 return value
1012 else:
1013 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001014
1015 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001016 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001017 if var:
1018 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001019 else:
1020 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001021
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001022 def get_var_obj(self, name, vartype=None):
1023 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001024 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001025 # create a Tkinter variable object with self.text as master:
1026 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001027 return var
1028
1029 # Tk implementations of "virtual text methods" -- each platform
1030 # reusing IDLE's support code needs to define these for its GUI's
1031 # flavor of widget.
1032
1033 # Is character at text_index in a Python string? Return 0 for
1034 # "guaranteed no", true for anything else. This info is expensive
1035 # to compute ab initio, but is probably already known by the
1036 # platform's colorizer.
1037
1038 def is_char_in_string(self, text_index):
1039 if self.color:
1040 # Return true iff colorizer hasn't (re)gotten this far
1041 # yet, or the character is tagged as being in a string
1042 return self.text.tag_prevrange("TODO", text_index) or \
1043 "STRING" in self.text.tag_names(text_index)
1044 else:
1045 # The colorizer is missing: assume the worst
1046 return 1
1047
1048 # If a selection is defined in the text widget, return (start,
1049 # end) as Tkinter text indices, otherwise return (None, None)
1050 def get_selection_indices(self):
1051 try:
1052 first = self.text.index("sel.first")
1053 last = self.text.index("sel.last")
1054 return first, last
1055 except TclError:
1056 return None, None
1057
1058 # Return the text widget's current view of what a tab stop means
1059 # (equivalent width in spaces).
1060
1061 def get_tabwidth(self):
1062 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1063 return int(current)
1064
1065 # Set the text widget's current view of what a tab stop means.
1066
1067 def set_tabwidth(self, newtabwidth):
1068 text = self.text
1069 if self.get_tabwidth() != newtabwidth:
1070 pixels = text.tk.call("font", "measure", text["font"],
1071 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001072 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001073 text.configure(tabs=pixels)
1074
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001075 # If ispythonsource and guess are true, guess a good value for
1076 # indentwidth based on file content (if possible), and if
1077 # indentwidth != tabwidth set usetabs false.
1078 # In any case, adjust the Text widget's view of what a tab
1079 # character means.
1080
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001081 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001082 if guess and ispythonsource:
1083 i = self.guess_indent()
1084 if 2 <= i <= 8:
1085 self.indentwidth = i
1086 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001087 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001088 self.set_tabwidth(self.tabwidth)
1089
1090 def smart_backspace_event(self, event):
1091 text = self.text
1092 first, last = self.get_selection_indices()
1093 if first and last:
1094 text.delete(first, last)
1095 text.mark_set("insert", first)
1096 return "break"
1097 # Delete whitespace left, until hitting a real char or closest
1098 # preceding virtual tab stop.
1099 chars = text.get("insert linestart", "insert")
1100 if chars == '':
1101 if text.compare("insert", ">", "1.0"):
1102 # easy: delete preceding newline
1103 text.delete("insert-1c")
1104 else:
1105 text.bell() # at start of buffer
1106 return "break"
1107 if chars[-1] not in " \t":
1108 # easy: delete preceding real char
1109 text.delete("insert-1c")
1110 return "break"
1111 # Ick. It may require *inserting* spaces if we back up over a
1112 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001113 tabwidth = self.tabwidth
1114 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001115 assert have > 0
1116 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001117 # Debug prompt is multilined....
1118 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001119 ncharsdeleted = 0
1120 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001121 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001122 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001123 chars = chars[:-1]
1124 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001125 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001126 if have <= want or chars[-1] not in " \t":
1127 break
1128 text.undo_block_start()
1129 text.delete("insert-%dc" % ncharsdeleted, "insert")
1130 if have < want:
1131 text.insert("insert", ' ' * (want - have))
1132 text.undo_block_stop()
1133 return "break"
1134
1135 def smart_indent_event(self, event):
1136 # if intraline selection:
1137 # delete it
1138 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001139 # do indent-region
1140 # else:
1141 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001142 text = self.text
1143 first, last = self.get_selection_indices()
1144 text.undo_block_start()
1145 try:
1146 if first and last:
1147 if index2line(first) != index2line(last):
1148 return self.indent_region_event(event)
1149 text.delete(first, last)
1150 text.mark_set("insert", first)
1151 prefix = text.get("insert linestart", "insert")
1152 raw, effective = classifyws(prefix, self.tabwidth)
1153 if raw == len(prefix):
1154 # only whitespace to the left
1155 self.reindent_to(effective + self.indentwidth)
1156 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001157 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001158 if self.usetabs:
1159 pad = '\t'
1160 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001161 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001162 n = self.indentwidth
1163 pad = ' ' * (n - effective % n)
1164 text.insert("insert", pad)
1165 text.see("insert")
1166 return "break"
1167 finally:
1168 text.undo_block_stop()
1169
1170 def newline_and_indent_event(self, event):
1171 text = self.text
1172 first, last = self.get_selection_indices()
1173 text.undo_block_start()
1174 try:
1175 if first and last:
1176 text.delete(first, last)
1177 text.mark_set("insert", first)
1178 line = text.get("insert linestart", "insert")
1179 i, n = 0, len(line)
1180 while i < n and line[i] in " \t":
1181 i = i+1
1182 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001183 # the cursor is in or at leading indentation in a continuation
1184 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001185 text.insert("insert linestart", '\n')
1186 return "break"
1187 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001188 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001189 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001190 last_line_of_prompt = sys.ps1.split('\n')[-1]
1191 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001192 line = line[:-1]
1193 i = i+1
1194 if i:
1195 text.delete("insert - %d chars" % i, "insert")
1196 # strip whitespace after insert point
1197 while text.get("insert") in " \t":
1198 text.delete("insert")
1199 # start new line
1200 text.insert("insert", '\n')
1201
1202 # adjust indentation for continuations and block
1203 # open/close first need to find the last stmt
1204 lno = index2line(text.index('insert'))
1205 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001206 if not self.context_use_ps1:
1207 for context in self.num_context_lines:
1208 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001209 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001210 rawtext = text.get(startatindex, "insert")
1211 y.set_str(rawtext)
1212 bod = y.find_good_parse_start(
1213 self.context_use_ps1,
1214 self._build_char_in_string_func(startatindex))
1215 if bod is not None or startat == 1:
1216 break
1217 y.set_lo(bod or 0)
1218 else:
1219 r = text.tag_prevrange("console", "insert")
1220 if r:
1221 startatindex = r[1]
1222 else:
1223 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001224 rawtext = text.get(startatindex, "insert")
1225 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001226 y.set_lo(0)
1227
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001228 c = y.get_continuation_type()
1229 if c != PyParse.C_NONE:
1230 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001231 if c == PyParse.C_STRING_FIRST_LINE:
1232 # after the first line of a string; do not indent at all
1233 pass
1234 elif c == PyParse.C_STRING_NEXT_LINES:
1235 # inside a string which started before this line;
1236 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001237 text.insert("insert", indent)
1238 elif c == PyParse.C_BRACKET:
1239 # line up with the first (if any) element of the
1240 # last open bracket structure; else indent one
1241 # level beyond the indent of the line with the
1242 # last open bracket
1243 self.reindent_to(y.compute_bracket_indent())
1244 elif c == PyParse.C_BACKSLASH:
1245 # if more than one line in this stmt already, just
1246 # mimic the current indent; else if initial line
1247 # has a start on an assignment stmt, indent to
1248 # beyond leftmost =; else to beyond first chunk of
1249 # non-whitespace on initial line
1250 if y.get_num_lines_in_stmt() > 1:
1251 text.insert("insert", indent)
1252 else:
1253 self.reindent_to(y.compute_backslash_indent())
1254 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001255 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001256 return "break"
1257
1258 # This line starts a brand new stmt; indent relative to
1259 # indentation of initial line of closest preceding
1260 # interesting stmt.
1261 indent = y.get_base_indent_string()
1262 text.insert("insert", indent)
1263 if y.is_block_opener():
1264 self.smart_indent_event(event)
1265 elif indent and y.is_block_closer():
1266 self.smart_backspace_event(event)
1267 return "break"
1268 finally:
1269 text.see("insert")
1270 text.undo_block_stop()
1271
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001272 # Our editwin provides a is_char_in_string function that works
1273 # with a Tk text index, but PyParse only knows about offsets into
1274 # a string. This builds a function for PyParse that accepts an
1275 # offset.
1276
1277 def _build_char_in_string_func(self, startindex):
1278 def inner(offset, _startindex=startindex,
1279 _icis=self.is_char_in_string):
1280 return _icis(_startindex + "+%dc" % offset)
1281 return inner
1282
1283 def indent_region_event(self, event):
1284 head, tail, chars, lines = self.get_region()
1285 for pos in range(len(lines)):
1286 line = lines[pos]
1287 if line:
1288 raw, effective = classifyws(line, self.tabwidth)
1289 effective = effective + self.indentwidth
1290 lines[pos] = self._make_blanks(effective) + line[raw:]
1291 self.set_region(head, tail, chars, lines)
1292 return "break"
1293
1294 def dedent_region_event(self, event):
1295 head, tail, chars, lines = self.get_region()
1296 for pos in range(len(lines)):
1297 line = lines[pos]
1298 if line:
1299 raw, effective = classifyws(line, self.tabwidth)
1300 effective = max(effective - self.indentwidth, 0)
1301 lines[pos] = self._make_blanks(effective) + line[raw:]
1302 self.set_region(head, tail, chars, lines)
1303 return "break"
1304
1305 def comment_region_event(self, event):
1306 head, tail, chars, lines = self.get_region()
1307 for pos in range(len(lines) - 1):
1308 line = lines[pos]
1309 lines[pos] = '##' + line
1310 self.set_region(head, tail, chars, lines)
1311
1312 def uncomment_region_event(self, event):
1313 head, tail, chars, lines = self.get_region()
1314 for pos in range(len(lines)):
1315 line = lines[pos]
1316 if not line:
1317 continue
1318 if line[:2] == '##':
1319 line = line[2:]
1320 elif line[:1] == '#':
1321 line = line[1:]
1322 lines[pos] = line
1323 self.set_region(head, tail, chars, lines)
1324
1325 def tabify_region_event(self, event):
1326 head, tail, chars, lines = self.get_region()
1327 tabwidth = self._asktabwidth()
1328 for pos in range(len(lines)):
1329 line = lines[pos]
1330 if line:
1331 raw, effective = classifyws(line, tabwidth)
1332 ntabs, nspaces = divmod(effective, tabwidth)
1333 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1334 self.set_region(head, tail, chars, lines)
1335
1336 def untabify_region_event(self, event):
1337 head, tail, chars, lines = self.get_region()
1338 tabwidth = self._asktabwidth()
1339 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001340 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001341 self.set_region(head, tail, chars, lines)
1342
1343 def toggle_tabs_event(self, event):
1344 if self.askyesno(
1345 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001346 "Turn tabs " + ("on", "off")[self.usetabs] +
1347 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001348 ("will be", "remains at")[self.usetabs] + " 8." +
1349 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001350 parent=self.text):
1351 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001352 # Try to prevent inconsistent indentation.
1353 # User must change indent width manually after using tabs.
1354 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001355 return "break"
1356
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001357 # XXX this isn't bound to anything -- see tabwidth comments
1358## def change_tabwidth_event(self, event):
1359## new = self._asktabwidth()
1360## if new != self.tabwidth:
1361## self.tabwidth = new
1362## self.set_indentation_params(0, guess=0)
1363## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001364
1365 def change_indentwidth_event(self, event):
1366 new = self.askinteger(
1367 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001368 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001369 parent=self.text,
1370 initialvalue=self.indentwidth,
1371 minvalue=2,
1372 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001373 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001374 self.indentwidth = new
1375 return "break"
1376
1377 def get_region(self):
1378 text = self.text
1379 first, last = self.get_selection_indices()
1380 if first and last:
1381 head = text.index(first + " linestart")
1382 tail = text.index(last + "-1c lineend +1c")
1383 else:
1384 head = text.index("insert linestart")
1385 tail = text.index("insert lineend +1c")
1386 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001387 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001388 return head, tail, chars, lines
1389
1390 def set_region(self, head, tail, chars, lines):
1391 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001392 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001393 if newchars == chars:
1394 text.bell()
1395 return
1396 text.tag_remove("sel", "1.0", "end")
1397 text.mark_set("insert", head)
1398 text.undo_block_start()
1399 text.delete(head, tail)
1400 text.insert(head, newchars)
1401 text.undo_block_stop()
1402 text.tag_add("sel", head, "insert")
1403
1404 # Make string that displays as n leading blanks.
1405
1406 def _make_blanks(self, n):
1407 if self.usetabs:
1408 ntabs, nspaces = divmod(n, self.tabwidth)
1409 return '\t' * ntabs + ' ' * nspaces
1410 else:
1411 return ' ' * n
1412
1413 # Delete from beginning of line to insert point, then reinsert
1414 # column logical (meaning use tabs if appropriate) spaces.
1415
1416 def reindent_to(self, column):
1417 text = self.text
1418 text.undo_block_start()
1419 if text.compare("insert linestart", "!=", "insert"):
1420 text.delete("insert linestart", "insert")
1421 if column:
1422 text.insert("insert", self._make_blanks(column))
1423 text.undo_block_stop()
1424
1425 def _asktabwidth(self):
1426 return self.askinteger(
1427 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001428 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001429 parent=self.text,
1430 initialvalue=self.indentwidth,
1431 minvalue=2,
1432 maxvalue=16) or self.tabwidth
1433
1434 # Guess indentwidth from text content.
1435 # Return guessed indentwidth. This should not be believed unless
1436 # it's in a reasonable range (e.g., it will be 0 if no indented
1437 # blocks are found).
1438
1439 def guess_indent(self):
1440 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1441 if opener and indented:
1442 raw, indentsmall = classifyws(opener, self.tabwidth)
1443 raw, indentlarge = classifyws(indented, self.tabwidth)
1444 else:
1445 indentsmall = indentlarge = 0
1446 return indentlarge - indentsmall
1447
1448# "line.col" -> line, as an int
1449def index2line(index):
1450 return int(float(index))
1451
1452# Look at the leading whitespace in s.
1453# Return pair (# of leading ws characters,
1454# effective # of leading blanks after expanding
1455# tabs to width tabwidth)
1456
1457def classifyws(s, tabwidth):
1458 raw = effective = 0
1459 for ch in s:
1460 if ch == ' ':
1461 raw = raw + 1
1462 effective = effective + 1
1463 elif ch == '\t':
1464 raw = raw + 1
1465 effective = (effective // tabwidth + 1) * tabwidth
1466 else:
1467 break
1468 return raw, effective
1469
1470import tokenize
1471_tokenize = tokenize
1472del tokenize
1473
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001474class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001475
1476 # .run() chews over the Text widget, looking for a block opener
1477 # and the stmt following it. Returns a pair,
1478 # (line containing block opener, line containing stmt)
1479 # Either or both may be None.
1480
1481 def __init__(self, text, tabwidth):
1482 self.text = text
1483 self.tabwidth = tabwidth
1484 self.i = self.finished = 0
1485 self.blkopenline = self.indentedline = None
1486
1487 def readline(self):
1488 if self.finished:
1489 return ""
1490 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001491 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001492 if self.text.compare(mark, ">=", "end"):
1493 return ""
1494 return self.text.get(mark, mark + " lineend+1c")
1495
1496 def tokeneater(self, type, token, start, end, line,
1497 INDENT=_tokenize.INDENT,
1498 NAME=_tokenize.NAME,
1499 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1500 if self.finished:
1501 pass
1502 elif type == NAME and token in OPENERS:
1503 self.blkopenline = line
1504 elif type == INDENT and self.blkopenline:
1505 self.indentedline = line
1506 self.finished = 1
1507
1508 def run(self):
1509 save_tabsize = _tokenize.tabsize
1510 _tokenize.tabsize = self.tabwidth
1511 try:
1512 try:
1513 _tokenize.tokenize(self.readline, self.tokeneater)
1514 except _tokenize.TokenError:
1515 # since we cut off the tokenizer early, we can trigger
1516 # spurious errors
1517 pass
1518 finally:
1519 _tokenize.tabsize = save_tabsize
1520 return self.blkopenline, self.indentedline
1521
1522### end autoindent code ###
1523
David Scherer7aced172000-08-15 01:13:23 +00001524def prepstr(s):
1525 # Helper to extract the underscore from a string, e.g.
1526 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001527 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001528 if i >= 0:
1529 s = s[:i] + s[i+1:]
1530 return i, s
1531
1532
1533keynames = {
1534 'bracketleft': '[',
1535 'bracketright': ']',
1536 'slash': '/',
1537}
1538
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001539def get_accelerator(keydefs, eventname):
1540 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001541 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1542 # if not keylist:
1543 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1544 "<<open-module>>",
1545 "<<goto-line>>",
1546 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001547 return ""
1548 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001549 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001550 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1551 s = re.sub("Key-", "", s)
1552 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1553 s = re.sub("Control-", "Ctrl-", s)
1554 s = re.sub("-", "+", s)
1555 s = re.sub("><", " ", s)
1556 s = re.sub("<", "", s)
1557 s = re.sub(">", "", s)
1558 return s
1559
1560
1561def fixwordbreaks(root):
1562 # Make sure that Tk's double-click and next/previous word
1563 # operations use our definition of a word (i.e. an identifier)
1564 tk = root.tk
1565 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1566 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1567 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1568
1569
1570def test():
1571 root = Tk()
1572 fixwordbreaks(root)
1573 root.withdraw()
1574 if sys.argv[1:]:
1575 filename = sys.argv[1]
1576 else:
1577 filename = None
1578 edit = EditorWindow(root=root, filename=filename)
1579 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001580 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001581 root.mainloop()
1582 root.destroy()
1583
1584if __name__ == '__main__':
1585 test()