blob: cee7767d3b5b33126818fe379a528e8a4b3c9299 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
David Scherer7aced172000-08-15 01:13:23 +00003import re
Guido van Rossum33d26892007-08-05 15:29:28 +00004import string
David Scherer7aced172000-08-15 01:13:23 +00005import imp
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00006from itertools import count
Georg Brandl14fc4272008-05-17 18:39:55 +00007from tkinter import *
8import tkinter.simpledialog as tkSimpleDialog
9import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000010import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000011import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000012
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000013from idlelib.MultiCall import MultiCallCreator
14from idlelib import idlever
15from idlelib import WindowList
16from idlelib import SearchDialog
17from idlelib import GrepDialog
18from idlelib import ReplaceDialog
19from idlelib import PyParse
20from idlelib.configHandler import idleConf
21from idlelib import aboutDialog, textView, configDialog
22from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000023
24# The default tab setting for a Text widget, in average-width characters.
25TK_TABWIDTH_DEFAULT = 8
26
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000027def _find_module(fullname, path=None):
28 """Version of imp.find_module() that handles hierarchical module names"""
29
30 file = None
31 for tgt in fullname.split('.'):
32 if file is not None:
33 file.close() # close intermediate files
34 (file, filename, descr) = imp.find_module(tgt, path)
35 if descr[2] == imp.PY_SOURCE:
36 break # find but not load the source file
37 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000038 try:
39 path = module.__path__
40 except AttributeError:
Kurt B. Kaiserad667422007-08-23 01:06:15 +000041 raise ImportError('No source for module ' + module.__name__)
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000042 return file, filename, descr
43
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000044class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000045 from idlelib.Percolator import Percolator
46 from idlelib.ColorDelegator import ColorDelegator
47 from idlelib.UndoDelegator import UndoDelegator
48 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
49 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000050 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000051 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000052
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000053 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000054
55 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000056 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000057 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000058 if sys.platform.count('linux'):
59 # look for html docs in a couple of standard places
60 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
61 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
62 dochome = '/var/www/html/python/index.html'
63 else:
64 basepath = '/usr/share/doc/' # standard location
65 dochome = os.path.join(basepath, pyver,
66 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000067 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000068 chmfile = os.path.join(sys.prefix, 'Doc',
69 'Python%d%d.chm' % sys.version_info[:2])
Thomas Heller84ef1532003-09-23 20:53:10 +000070 if os.path.isfile(chmfile):
71 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +000072
73 elif macosxSupport.runningAsOSXApp():
74 # documentation is stored inside the python framework
75 dochome = os.path.join(sys.prefix,
76 'Resources/English.lproj/Documentation/index.html')
77
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000078 dochome = os.path.normpath(dochome)
79 if os.path.isfile(dochome):
80 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +000081 if sys.platform == 'darwin':
82 # Safari requires real file:-URLs
83 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000084 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +000085 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +000086 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000087 self.flist = flist
88 root = root or flist.root
89 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +000090 try:
91 sys.ps1
92 except AttributeError:
93 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +000094 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +000095 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000096 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +000097 self.tkinter_vars = flist.vars
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +000098 #self.top.instance_dict makes flist.inversedict avalable to
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000099 #configDialog.py so it can access all EditorWindow instaces
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000100 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000101 else:
102 self.tkinter_vars = {} # keys: Tkinter event names
103 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000104 self.top.instance_dict = {}
105 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000106 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000107 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000108 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000109 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000110 text_options = {
111 'name': 'text',
112 'padx': 5,
113 'wrap': 'none',
114 'width': self.width,
115 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
116 if TkVersion >= 8.5:
117 # Starting with tk 8.5 we have to set the new tabstyle option
118 # to 'wordprocessor' to achieve the same display of tabs as in
119 # older tk versions.
120 text_options['tabstyle'] = 'wordprocessor'
121 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000122 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000123
124 self.createmenubar()
125 self.apply_bindings()
126
127 self.top.protocol("WM_DELETE_WINDOW", self.close)
128 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000129 if macosxSupport.runningAsOSXApp():
130 # Command-W on editorwindows doesn't work without this.
131 text.bind('<<close-window>>', self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000132 text.bind("<<cut>>", self.cut)
133 text.bind("<<copy>>", self.copy)
134 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000135 text.bind("<<center-insert>>", self.center_insert_event)
136 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000137 text.bind("<<python-docs>>", self.python_docs)
138 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000139 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000140 text.bind("<<open-module>>", self.open_module)
141 text.bind("<<do-nothing>>", lambda event: "break")
142 text.bind("<<select-all>>", self.select_all)
143 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000144 text.bind("<<find>>", self.find_event)
145 text.bind("<<find-again>>", self.find_again_event)
146 text.bind("<<find-in-files>>", self.find_in_files_event)
147 text.bind("<<find-selection>>", self.find_selection_event)
148 text.bind("<<replace>>", self.replace_event)
149 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000150 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000151 text.bind("<<smart-backspace>>",self.smart_backspace_event)
152 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
153 text.bind("<<smart-indent>>",self.smart_indent_event)
154 text.bind("<<indent-region>>",self.indent_region_event)
155 text.bind("<<dedent-region>>",self.dedent_region_event)
156 text.bind("<<comment-region>>",self.comment_region_event)
157 text.bind("<<uncomment-region>>",self.uncomment_region_event)
158 text.bind("<<tabify-region>>",self.tabify_region_event)
159 text.bind("<<untabify-region>>",self.untabify_region_event)
160 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
161 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000162 text.bind("<Left>", self.move_at_edge_if_selection(0))
163 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000164 text.bind("<<del-word-left>>", self.del_word_left)
165 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000166 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000167
David Scherer7aced172000-08-15 01:13:23 +0000168 if flist:
169 flist.inversedict[self] = key
170 if key:
171 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000172 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000173 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
174 text.bind("<<open-class-browser>>", self.open_class_browser)
175 text.bind("<<open-path-browser>>", self.open_path_browser)
176
Steven M. Gava898a3652001-10-07 11:10:44 +0000177 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000178 vbar['command'] = text.yview
179 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000180 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000181 fontWeight = 'normal'
182 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000183 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000184 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
185 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
186 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000187 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
188 text.pack(side=TOP, fill=BOTH, expand=1)
189 text.focus_set()
190
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000191 # usetabs true -> literal tab characters are used by indent and
192 # dedent cmds, possibly mixed with spaces if
193 # indentwidth is not a multiple of tabwidth,
194 # which will cause Tabnanny to nag!
195 # false -> tab characters are converted to spaces by indent
196 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000197 # Although use-spaces=0 can be configured manually in config-main.def,
198 # configuration of tabs v. spaces is not supported in the configuration
199 # dialog. IDLE promotes the preferred Python indentation: use spaces!
200 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
201 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000202
203 # tabwidth is the display width of a literal tab character.
204 # CAUTION: telling Tk to use anything other than its default
205 # tab setting causes it to use an entirely different tabbing algorithm,
206 # treating tab stops as fixed distances from the left margin.
207 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000208 self.tabwidth = 8 # must remain 8 until Tk is fixed.
209
210 # indentwidth is the number of screen characters per indent level.
211 # The recommended Python indentation is four spaces.
212 self.indentwidth = self.tabwidth
213 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000214
215 # If context_use_ps1 is true, parsing searches back for a ps1 line;
216 # else searches for a popular (if, def, ...) Python stmt.
217 self.context_use_ps1 = False
218
219 # When searching backwards for a reliable place to begin parsing,
220 # first start num_context_lines[0] lines back, then
221 # num_context_lines[1] lines back if that didn't work, and so on.
222 # The last value should be huge (larger than the # of lines in a
223 # conceivable file).
224 # Making the initial values larger slows things down more often.
225 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000226 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000227 self.undo = undo = self.UndoDelegator()
228 per.insertfilter(undo)
229 text.undo_block_start = undo.undo_block_start
230 text.undo_block_stop = undo.undo_block_stop
231 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000232 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000233 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000234 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000235 self.good_load = False
236 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000237 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000238 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000239 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000240 if io.loadfile(filename):
241 self.good_load = True
242 is_py_src = self.ispythonsource(filename)
243 self.set_indentation_params(is_py_src)
244 if is_py_src:
245 self.color = color = self.ColorDelegator()
246 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000247 else:
248 io.set_filename(filename)
Christian Heimesa156e092008-02-16 07:38:31 +0000249 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000250 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000251 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000252 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000253 menu = self.menudict.get('windows')
254 if menu:
255 end = menu.index("end")
256 if end is None:
257 end = -1
258 if end >= 0:
259 menu.add_separator()
260 end = end + 1
261 self.wmenu_end = end
262 WindowList.register_callback(self.postwindowsmenu)
263
264 # Some abstractions so IDLE extensions are cross-IDE
265 self.askyesno = tkMessageBox.askyesno
266 self.askinteger = tkSimpleDialog.askinteger
267 self.showerror = tkMessageBox.showerror
268
Martin v. Löwis307021f2005-11-27 16:59:04 +0000269 def _filename_to_unicode(self, filename):
270 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000271 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000272 return filename
273 else:
274 try:
275 return filename.decode(self.filesystemencoding)
276 except UnicodeDecodeError:
277 # XXX
278 try:
279 return filename.decode(self.encoding)
280 except UnicodeDecodeError:
281 # byte-to-byte conversion
282 return filename.decode('iso8859-1')
283
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000284 def new_callback(self, event):
285 dirname, basename = self.io.defaultfilename()
286 self.flist.new(dirname)
287 return "break"
288
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000289 def home_callback(self, event):
290 if (event.state & 12) != 0 and event.keysym == "Home":
291 # state&1==shift, state&4==control, state&8==alt
292 return # <Modifier-Home>; fall back to class binding
293
294 if self.text.index("iomark") and \
295 self.text.compare("iomark", "<=", "insert lineend") and \
296 self.text.compare("insert linestart", "<=", "iomark"):
297 insertpt = int(self.text.index("iomark").split(".")[1])
298 else:
299 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000300 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000301 if line[insertpt] not in (' ','\t'):
302 break
303 else:
304 insertpt=len(line)
305
306 lineat = int(self.text.index("insert").split('.')[1])
307
308 if insertpt == lineat:
309 insertpt = 0
310
311 dest = "insert linestart+"+str(insertpt)+"c"
312
313 if (event.state&1) == 0:
314 # shift not pressed
315 self.text.tag_remove("sel", "1.0", "end")
316 else:
317 if not self.text.index("sel.first"):
318 self.text.mark_set("anchor","insert")
319
320 first = self.text.index(dest)
321 last = self.text.index("anchor")
322
323 if self.text.compare(first,">",last):
324 first,last = last,first
325
326 self.text.tag_remove("sel", "1.0", "end")
327 self.text.tag_add("sel", first, last)
328
329 self.text.mark_set("insert", dest)
330 self.text.see("insert")
331 return "break"
332
David Scherer7aced172000-08-15 01:13:23 +0000333 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000334 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000335 if macosxSupport.runningAsOSXApp():
336 # Insert some padding to avoid obscuring some of the statusbar
337 # by the resize widget.
338 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000339 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
340 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
341 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000342 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
343 self.text.event_add("<<set-line-and-column>>",
344 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000345 self.text.after_idle(self.set_line_and_column)
346
347 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000348 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000349 self.status_bar.set_label('column', 'Col: %s' % column)
350 self.status_bar.set_label('line', 'Ln: %s' % line)
351
David Scherer7aced172000-08-15 01:13:23 +0000352 menu_specs = [
353 ("file", "_File"),
354 ("edit", "_Edit"),
355 ("format", "F_ormat"),
356 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000357 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000358 ("windows", "_Windows"),
359 ("help", "_Help"),
360 ]
361
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000362 if macosxSupport.runningAsOSXApp():
363 del menu_specs[-3]
364 menu_specs[-2] = ("windows", "_Window")
365
366
David Scherer7aced172000-08-15 01:13:23 +0000367 def createmenubar(self):
368 mbar = self.menubar
369 self.menudict = menudict = {}
370 for name, label in self.menu_specs:
371 underline, label = prepstr(label)
372 menudict[name] = menu = Menu(mbar, name=name)
373 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren827822e2009-02-12 15:01:44 +0000374 if macosxSupport.runningAsOSXApp():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000375 # Insert the application menu
376 menudict['application'] = menu = Menu(mbar, name='apple')
377 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000378 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000379 self.recent_files_menu = Menu(self.menubar)
380 self.menudict['file'].insert_cascade(3, label='Recent Files',
381 underline=0,
382 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000383 self.base_helpmenu_length = self.menudict['help'].index(END)
384 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000385
386 def postwindowsmenu(self):
387 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000388 menu = self.menudict['windows']
389 end = menu.index("end")
390 if end is None:
391 end = -1
392 if end > self.wmenu_end:
393 menu.delete(self.wmenu_end+1, end)
394 WindowList.add_windows_to_menu(menu)
395
396 rmenu = None
397
398 def right_menu_event(self, event):
399 self.text.tag_remove("sel", "1.0", "end")
400 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
401 if not self.rmenu:
402 self.make_rmenu()
403 rmenu = self.rmenu
404 self.event = event
405 iswin = sys.platform[:3] == 'win'
406 if iswin:
407 self.text.config(cursor="arrow")
408 rmenu.tk_popup(event.x_root, event.y_root)
409 if iswin:
410 self.text.config(cursor="ibeam")
411
412 rmenu_specs = [
413 # ("Label", "<<virtual-event>>"), ...
414 ("Close", "<<close-window>>"), # Example
415 ]
416
417 def make_rmenu(self):
418 rmenu = Menu(self.text, tearoff=0)
419 for label, eventname in self.rmenu_specs:
420 def command(text=self.text, eventname=eventname):
421 text.event_generate(eventname)
422 rmenu.add_command(label=label, command=command)
423 self.rmenu = rmenu
424
425 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000426 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000427
Steven M. Gava3b55a892001-11-21 05:56:26 +0000428 def config_dialog(self, event=None):
429 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000430
David Scherer7aced172000-08-15 01:13:23 +0000431 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000432 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000433 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000434
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000435 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000436 if sys.platform[:3] == 'win':
Steven M. Gava931625d2002-04-22 00:38:26 +0000437 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000438 else:
439 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000440 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000441
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000442 def cut(self,event):
443 self.text.event_generate("<<Cut>>")
444 return "break"
445
446 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000447 if not self.text.tag_ranges("sel"):
448 # There is no selection, so do nothing and maybe interrupt.
449 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000450 self.text.event_generate("<<Copy>>")
451 return "break"
452
453 def paste(self,event):
454 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000455 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000456 return "break"
457
David Scherer7aced172000-08-15 01:13:23 +0000458 def select_all(self, event=None):
459 self.text.tag_add("sel", "1.0", "end-1c")
460 self.text.mark_set("insert", "1.0")
461 self.text.see("insert")
462 return "break"
463
464 def remove_selection(self, event=None):
465 self.text.tag_remove("sel", "1.0", "end")
466 self.text.see("insert")
467
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000468 def move_at_edge_if_selection(self, edge_index):
469 """Cursor move begins at start or end of selection
470
471 When a left/right cursor key is pressed create and return to Tkinter a
472 function which causes a cursor move from the associated edge of the
473 selection.
474
475 """
476 self_text_index = self.text.index
477 self_text_mark_set = self.text.mark_set
478 edges_table = ("sel.first+1c", "sel.last-1c")
479 def move_at_edge(event):
480 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
481 try:
482 self_text_index("sel.first")
483 self_text_mark_set("insert", edges_table[edge_index])
484 except TclError:
485 pass
486 return move_at_edge
487
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000488 def del_word_left(self, event):
489 self.text.event_generate('<Meta-Delete>')
490 return "break"
491
492 def del_word_right(self, event):
493 self.text.event_generate('<Meta-d>')
494 return "break"
495
Steven M. Gavac5976402002-01-04 03:06:08 +0000496 def find_event(self, event):
497 SearchDialog.find(self.text)
498 return "break"
499
500 def find_again_event(self, event):
501 SearchDialog.find_again(self.text)
502 return "break"
503
504 def find_selection_event(self, event):
505 SearchDialog.find_selection(self.text)
506 return "break"
507
508 def find_in_files_event(self, event):
509 GrepDialog.grep(self.text, self.io, self.flist)
510 return "break"
511
512 def replace_event(self, event):
513 ReplaceDialog.replace(self.text)
514 return "break"
515
516 def goto_line_event(self, event):
517 text = self.text
518 lineno = tkSimpleDialog.askinteger("Goto",
519 "Go to line number:",parent=text)
520 if lineno is None:
521 return "break"
522 if lineno <= 0:
523 text.bell()
524 return "break"
525 text.mark_set("insert", "%d.0" % lineno)
526 text.see("insert")
527
David Scherer7aced172000-08-15 01:13:23 +0000528 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000529 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000530 try:
531 name = self.text.get("sel.first", "sel.last")
532 except TclError:
533 name = ""
534 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000535 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000536 name = tkSimpleDialog.askstring("Module",
537 "Enter the name of a Python module\n"
538 "to search on sys.path and open:",
539 parent=self.text, initialvalue=name)
540 if name:
541 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000542 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000543 return
David Scherer7aced172000-08-15 01:13:23 +0000544 # XXX Ought to insert current file's directory in front of path
545 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000546 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000547 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000548 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
549 return
550 if type != imp.PY_SOURCE:
551 tkMessageBox.showerror("Unsupported type",
552 "%s is not a source module" % name, parent=self.text)
553 return
554 if f:
555 f.close()
556 if self.flist:
557 self.flist.open(file)
558 else:
559 self.io.loadfile(file)
560
561 def open_class_browser(self, event=None):
562 filename = self.io.filename
563 if not filename:
564 tkMessageBox.showerror(
565 "No filename",
566 "This buffer has no associated filename",
567 master=self.text)
568 self.text.focus_set()
569 return None
570 head, tail = os.path.split(filename)
571 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000572 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000573 ClassBrowser.ClassBrowser(self.flist, base, [head])
574
575 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000576 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000577 PathBrowser.PathBrowser(self.flist)
578
579 def gotoline(self, lineno):
580 if lineno is not None and lineno > 0:
581 self.text.mark_set("insert", "%d.0" % lineno)
582 self.text.tag_remove("sel", "1.0", "end")
583 self.text.tag_add("sel", "insert", "insert +1l")
584 self.center()
585
586 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000587 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000588 return True
David Scherer7aced172000-08-15 01:13:23 +0000589 base, ext = os.path.splitext(os.path.basename(filename))
590 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000591 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000592 line = self.text.get('1.0', '1.0 lineend')
593 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000594
595 def close_hook(self):
596 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000597 self.flist.unregister_maybe_terminate(self)
598 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000599
600 def set_close_hook(self, close_hook):
601 self.close_hook = close_hook
602
603 def filename_change_hook(self):
604 if self.flist:
605 self.flist.filename_changed_edit(self)
606 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000607 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000608 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000609
Christian Heimesa156e092008-02-16 07:38:31 +0000610 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000611 if self.color:
612 return
Christian Heimesa156e092008-02-16 07:38:31 +0000613 if self.ispythonsource(self.io.filename):
614 self.color = self.ColorDelegator()
615 # can add more colorizers here...
616 if self.color:
617 self.per.removefilter(self.undo)
618 self.per.insertfilter(self.color)
619 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000620
Christian Heimesa156e092008-02-16 07:38:31 +0000621 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000622 if not self.color:
623 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000624 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000625 self.per.removefilter(self.color)
626 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000627
Steven M. Gavab77d3432002-03-02 07:16:21 +0000628 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000629 "Update the colour theme"
630 # Called from self.filename_change_hook and from configDialog.py
631 self._rmcolorizer()
632 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000633 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000634 normal_colors = idleConf.GetHighlight(theme, 'normal')
635 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
636 select_colors = idleConf.GetHighlight(theme, 'hilite')
637 self.text.config(
638 foreground=normal_colors['foreground'],
639 background=normal_colors['background'],
640 insertbackground=cursor_color,
641 selectforeground=select_colors['foreground'],
642 selectbackground=select_colors['background'],
643 )
David Scherer7aced172000-08-15 01:13:23 +0000644
Guido van Rossum33d26892007-08-05 15:29:28 +0000645 IDENTCHARS = string.ascii_letters + string.digits + "_"
646
647 def colorize_syntax_error(self, text, pos):
648 text.tag_add("ERROR", pos)
649 char = text.get(pos)
650 if char and char in self.IDENTCHARS:
651 text.tag_add("ERROR", pos + " wordstart", pos)
652 if '\n' == text.get(pos): # error at line end
653 text.mark_set("insert", pos)
654 else:
655 text.mark_set("insert", pos + "+1c")
656 text.see(pos)
657
Steven M. Gavab1585412002-03-12 00:21:56 +0000658 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000659 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000660 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000661 fontWeight='normal'
662 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
663 fontWeight='bold'
664 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
665 idleConf.GetOption('main','EditorWindow','font-size'),
666 fontWeight))
667
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000668 def RemoveKeybindings(self):
669 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000670 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000671 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000672 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000673 self.text.event_delete(event, *keylist)
674 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000675 xkeydefs = idleConf.GetExtensionBindings(extensionName)
676 if xkeydefs:
677 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000678 self.text.event_delete(event, *keylist)
679
680 def ApplyKeybindings(self):
681 "Update the keybindings after they are changed"
682 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000683 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000684 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000685 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 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000689 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000690 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000691 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000692 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000693 for item in menu[1]:
694 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000695 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000696 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000697 menu = self.menudict[menubarItem]
698 end = menu.index(END) + 1
699 for index in range(0, end):
700 if menu.type(index) == 'command':
701 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000702 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000703 itemName = menu.entrycget(index, 'label')
704 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000705 if menubarItem in menuEventDict:
706 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000707 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000708 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000709 accel = get_accelerator(keydefs, event)
710 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000711
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000712 def set_notabs_indentwidth(self):
713 "Update the indentwidth if changed and not using tabs in this window"
714 # Called from configDialog.py
715 if not self.usetabs:
716 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
717 type='int')
718
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000719 def reset_help_menu_entries(self):
720 "Update the additional help entries on the Help menu"
721 help_list = idleConf.GetAllExtraHelpSourcesList()
722 helpmenu = self.menudict['help']
723 # first delete the extra help entries, if any
724 helpmenu_length = helpmenu.index(END)
725 if helpmenu_length > self.base_helpmenu_length:
726 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
727 # then rebuild them
728 if help_list:
729 helpmenu.add_separator()
730 for entry in help_list:
731 cmd = self.__extra_help_callback(entry[1])
732 helpmenu.add_command(label=entry[0], command=cmd)
733 # and update the menu dictionary
734 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000735
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000736 def __extra_help_callback(self, helpfile):
737 "Create a callback with the helpfile value frozen at definition time"
738 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000739 if not helpfile.startswith(('www', 'http')):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000740 url = os.path.normpath(helpfile)
741 if sys.platform[:3] == 'win':
742 os.startfile(helpfile)
743 else:
744 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000745 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000746
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000747 def update_recent_files_list(self, new_file=None):
748 "Load and update the recent files list and menus"
749 rf_list = []
750 if os.path.exists(self.recent_files_path):
751 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000752 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000753 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000754 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000755 rf_list_file.close()
756 if new_file:
757 new_file = os.path.abspath(new_file) + '\n'
758 if new_file in rf_list:
759 rf_list.remove(new_file) # move to top
760 rf_list.insert(0, new_file)
761 # clean and save the recent files list
762 bad_paths = []
763 for path in rf_list:
764 if '\0' in path or not os.path.exists(path[0:-1]):
765 bad_paths.append(path)
766 rf_list = [path for path in rf_list if path not in bad_paths]
767 ulchars = "1234567890ABCDEFGHIJK"
768 rf_list = rf_list[0:len(ulchars)]
769 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000770 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000771 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000772 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000773 rf_file.close()
774 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000775 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000776 menu = instance.recent_files_menu
777 menu.delete(1, END) # clear, and rebuild:
778 for i, file in zip(count(), rf_list):
779 file_name = file[0:-1] # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000780 # make unicode string to display non-ASCII chars correctly
781 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000782 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000783 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000784 command=callback,
785 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000786
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000787 def __recent_file_callback(self, file_name):
788 def open_recent_file(fn_closure=file_name):
789 self.io.open(editFile=fn_closure)
790 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000791
David Scherer7aced172000-08-15 01:13:23 +0000792 def saved_change_hook(self):
793 short = self.short_title()
794 long = self.long_title()
795 if short and long:
796 title = short + " - " + long
797 elif short:
798 title = short
799 elif long:
800 title = long
801 else:
802 title = "Untitled"
803 icon = short or long or title
804 if not self.get_saved():
805 title = "*%s*" % title
806 icon = "*%s" % icon
807 self.top.wm_title(title)
808 self.top.wm_iconname(icon)
809
810 def get_saved(self):
811 return self.undo.get_saved()
812
813 def set_saved(self, flag):
814 self.undo.set_saved(flag)
815
816 def reset_undo(self):
817 self.undo.reset_undo()
818
819 def short_title(self):
820 filename = self.io.filename
821 if filename:
822 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000823 # return unicode string to display non-ASCII chars correctly
824 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000825
826 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000827 # return unicode string to display non-ASCII chars correctly
828 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000829
830 def center_insert_event(self, event):
831 self.center()
832
833 def center(self, mark="insert"):
834 text = self.text
835 top, bot = self.getwindowlines()
836 lineno = self.getlineno(mark)
837 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000838 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000839 text.yview(float(newtop))
840
841 def getwindowlines(self):
842 text = self.text
843 top = self.getlineno("@0,0")
844 bot = self.getlineno("@0,65535")
845 if top == bot and text.winfo_height() == 1:
846 # Geometry manager hasn't run yet
847 height = int(text['height'])
848 bot = top + height - 1
849 return top, bot
850
851 def getlineno(self, mark="insert"):
852 text = self.text
853 return int(float(text.index(mark)))
854
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000855 def get_geometry(self):
856 "Return (width, height, x, y)"
857 geom = self.top.wm_geometry()
858 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000859 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000860
David Scherer7aced172000-08-15 01:13:23 +0000861 def close_event(self, event):
862 self.close()
863
864 def maybesave(self):
865 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000866 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000867 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000868 self.top.deiconify()
869 self.top.lower()
870 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000871 return self.io.maybesave()
872
873 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000874 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000875 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000876 self._close()
877 return reply
878
879 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000880 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000881 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000882 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000883 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000884 self.io.close()
885 self.io = None
886 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000887 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000888 self.color.close(False)
889 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000890 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000891 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000892 self.per.close()
893 self.per = None
894 self.top.destroy()
895 if self.close_hook:
896 # unless override: unregister from flist, terminate if last window
897 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000898
899 def load_extensions(self):
900 self.extensions = {}
901 self.load_standard_extensions()
902
903 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000904 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000905 if hasattr(ins, "close"):
906 ins.close()
907 self.extensions = {}
908
909 def load_standard_extensions(self):
910 for name in self.get_standard_extension_names():
911 try:
912 self.load_extension(name)
913 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000914 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000915 traceback.print_exc()
916
917 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000918 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000919
920 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000921 try:
922 mod = __import__(name, globals(), locals(), [])
923 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000924 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000925 raise
David Scherer7aced172000-08-15 01:13:23 +0000926 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000927 keydefs = idleConf.GetExtensionBindings(name)
928 if hasattr(cls, "menudefs"):
929 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000930 ins = cls(self)
931 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000932 if keydefs:
933 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000934 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000935 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000936 while methodname[:1] == '<':
937 methodname = methodname[1:]
938 while methodname[-1:] == '>':
939 methodname = methodname[:-1]
940 methodname = methodname + "_event"
941 if hasattr(ins, methodname):
942 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000943
944 def apply_bindings(self, keydefs=None):
945 if keydefs is None:
946 keydefs = self.Bindings.default_keydefs
947 text = self.text
948 text.keydefs = keydefs
949 for event, keylist in keydefs.items():
950 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000951 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000952
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000953 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000954 """Add appropriate entries to the menus and submenus
955
956 Menus that are absent or None in self.menudict are ignored.
957 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000958 if menudefs is None:
959 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000960 if keydefs is None:
961 keydefs = self.Bindings.default_keydefs
962 menudict = self.menudict
963 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000964 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000965 menu = menudict.get(mname)
966 if not menu:
967 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000968 for entry in entrylist:
969 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000970 menu.add_separator()
971 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000972 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000973 checkbutton = (label[:1] == '!')
974 if checkbutton:
975 label = label[1:]
976 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000977 accelerator = get_accelerator(keydefs, eventname)
978 def command(text=text, eventname=eventname):
979 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000980 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000981 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000982 menu.add_checkbutton(label=label, underline=underline,
983 command=command, accelerator=accelerator,
984 variable=var)
985 else:
986 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000987 command=command,
988 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000989
990 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000991 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +0000992 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000993 value = var.get()
994 return value
995 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000996 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +0000997
998 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000999 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001000 if var:
1001 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001002 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001003 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001004
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001005 def get_var_obj(self, name, vartype=None):
1006 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001007 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001008 # create a Tkinter variable object with self.text as master:
1009 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001010 return var
1011
1012 # Tk implementations of "virtual text methods" -- each platform
1013 # reusing IDLE's support code needs to define these for its GUI's
1014 # flavor of widget.
1015
1016 # Is character at text_index in a Python string? Return 0 for
1017 # "guaranteed no", true for anything else. This info is expensive
1018 # to compute ab initio, but is probably already known by the
1019 # platform's colorizer.
1020
1021 def is_char_in_string(self, text_index):
1022 if self.color:
1023 # Return true iff colorizer hasn't (re)gotten this far
1024 # yet, or the character is tagged as being in a string
1025 return self.text.tag_prevrange("TODO", text_index) or \
1026 "STRING" in self.text.tag_names(text_index)
1027 else:
1028 # The colorizer is missing: assume the worst
1029 return 1
1030
1031 # If a selection is defined in the text widget, return (start,
1032 # end) as Tkinter text indices, otherwise return (None, None)
1033 def get_selection_indices(self):
1034 try:
1035 first = self.text.index("sel.first")
1036 last = self.text.index("sel.last")
1037 return first, last
1038 except TclError:
1039 return None, None
1040
1041 # Return the text widget's current view of what a tab stop means
1042 # (equivalent width in spaces).
1043
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001044 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001045 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1046 return int(current)
1047
1048 # Set the text widget's current view of what a tab stop means.
1049
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001050 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001051 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001052 if self.get_tk_tabwidth() != newtabwidth:
1053 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001054 pixels = text.tk.call("font", "measure", text["font"],
1055 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001056 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001057 text.configure(tabs=pixels)
1058
Guido van Rossum33d26892007-08-05 15:29:28 +00001059### begin autoindent code ### (configuration was moved to beginning of class)
1060
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001061 def set_indentation_params(self, is_py_src, guess=True):
1062 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001063 i = self.guess_indent()
1064 if 2 <= i <= 8:
1065 self.indentwidth = i
1066 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001067 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001068 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001069
1070 def smart_backspace_event(self, event):
1071 text = self.text
1072 first, last = self.get_selection_indices()
1073 if first and last:
1074 text.delete(first, last)
1075 text.mark_set("insert", first)
1076 return "break"
1077 # Delete whitespace left, until hitting a real char or closest
1078 # preceding virtual tab stop.
1079 chars = text.get("insert linestart", "insert")
1080 if chars == '':
1081 if text.compare("insert", ">", "1.0"):
1082 # easy: delete preceding newline
1083 text.delete("insert-1c")
1084 else:
1085 text.bell() # at start of buffer
1086 return "break"
1087 if chars[-1] not in " \t":
1088 # easy: delete preceding real char
1089 text.delete("insert-1c")
1090 return "break"
1091 # Ick. It may require *inserting* spaces if we back up over a
1092 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001093 tabwidth = self.tabwidth
1094 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001095 assert have > 0
1096 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001097 # Debug prompt is multilined....
1098 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001099 ncharsdeleted = 0
1100 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001101 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001102 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001103 chars = chars[:-1]
1104 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001105 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001106 if have <= want or chars[-1] not in " \t":
1107 break
1108 text.undo_block_start()
1109 text.delete("insert-%dc" % ncharsdeleted, "insert")
1110 if have < want:
1111 text.insert("insert", ' ' * (want - have))
1112 text.undo_block_stop()
1113 return "break"
1114
1115 def smart_indent_event(self, event):
1116 # if intraline selection:
1117 # delete it
1118 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001119 # do indent-region
1120 # else:
1121 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001122 text = self.text
1123 first, last = self.get_selection_indices()
1124 text.undo_block_start()
1125 try:
1126 if first and last:
1127 if index2line(first) != index2line(last):
1128 return self.indent_region_event(event)
1129 text.delete(first, last)
1130 text.mark_set("insert", first)
1131 prefix = text.get("insert linestart", "insert")
1132 raw, effective = classifyws(prefix, self.tabwidth)
1133 if raw == len(prefix):
1134 # only whitespace to the left
1135 self.reindent_to(effective + self.indentwidth)
1136 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001137 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001138 if self.usetabs:
1139 pad = '\t'
1140 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001141 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001142 n = self.indentwidth
1143 pad = ' ' * (n - effective % n)
1144 text.insert("insert", pad)
1145 text.see("insert")
1146 return "break"
1147 finally:
1148 text.undo_block_stop()
1149
1150 def newline_and_indent_event(self, event):
1151 text = self.text
1152 first, last = self.get_selection_indices()
1153 text.undo_block_start()
1154 try:
1155 if first and last:
1156 text.delete(first, last)
1157 text.mark_set("insert", first)
1158 line = text.get("insert linestart", "insert")
1159 i, n = 0, len(line)
1160 while i < n and line[i] in " \t":
1161 i = i+1
1162 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001163 # the cursor is in or at leading indentation in a continuation
1164 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001165 text.insert("insert linestart", '\n')
1166 return "break"
1167 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001168 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001169 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001170 last_line_of_prompt = sys.ps1.split('\n')[-1]
1171 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001172 line = line[:-1]
1173 i = i+1
1174 if i:
1175 text.delete("insert - %d chars" % i, "insert")
1176 # strip whitespace after insert point
1177 while text.get("insert") in " \t":
1178 text.delete("insert")
1179 # start new line
1180 text.insert("insert", '\n')
1181
1182 # adjust indentation for continuations and block
1183 # open/close first need to find the last stmt
1184 lno = index2line(text.index('insert'))
1185 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001186 if not self.context_use_ps1:
1187 for context in self.num_context_lines:
1188 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001189 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001190 rawtext = text.get(startatindex, "insert")
1191 y.set_str(rawtext)
1192 bod = y.find_good_parse_start(
1193 self.context_use_ps1,
1194 self._build_char_in_string_func(startatindex))
1195 if bod is not None or startat == 1:
1196 break
1197 y.set_lo(bod or 0)
1198 else:
1199 r = text.tag_prevrange("console", "insert")
1200 if r:
1201 startatindex = r[1]
1202 else:
1203 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001204 rawtext = text.get(startatindex, "insert")
1205 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001206 y.set_lo(0)
1207
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001208 c = y.get_continuation_type()
1209 if c != PyParse.C_NONE:
1210 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001211 if c == PyParse.C_STRING_FIRST_LINE:
1212 # after the first line of a string; do not indent at all
1213 pass
1214 elif c == PyParse.C_STRING_NEXT_LINES:
1215 # inside a string which started before this line;
1216 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001217 text.insert("insert", indent)
1218 elif c == PyParse.C_BRACKET:
1219 # line up with the first (if any) element of the
1220 # last open bracket structure; else indent one
1221 # level beyond the indent of the line with the
1222 # last open bracket
1223 self.reindent_to(y.compute_bracket_indent())
1224 elif c == PyParse.C_BACKSLASH:
1225 # if more than one line in this stmt already, just
1226 # mimic the current indent; else if initial line
1227 # has a start on an assignment stmt, indent to
1228 # beyond leftmost =; else to beyond first chunk of
1229 # non-whitespace on initial line
1230 if y.get_num_lines_in_stmt() > 1:
1231 text.insert("insert", indent)
1232 else:
1233 self.reindent_to(y.compute_backslash_indent())
1234 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001235 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001236 return "break"
1237
1238 # This line starts a brand new stmt; indent relative to
1239 # indentation of initial line of closest preceding
1240 # interesting stmt.
1241 indent = y.get_base_indent_string()
1242 text.insert("insert", indent)
1243 if y.is_block_opener():
1244 self.smart_indent_event(event)
1245 elif indent and y.is_block_closer():
1246 self.smart_backspace_event(event)
1247 return "break"
1248 finally:
1249 text.see("insert")
1250 text.undo_block_stop()
1251
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001252 # Our editwin provides a is_char_in_string function that works
1253 # with a Tk text index, but PyParse only knows about offsets into
1254 # a string. This builds a function for PyParse that accepts an
1255 # offset.
1256
1257 def _build_char_in_string_func(self, startindex):
1258 def inner(offset, _startindex=startindex,
1259 _icis=self.is_char_in_string):
1260 return _icis(_startindex + "+%dc" % offset)
1261 return inner
1262
1263 def indent_region_event(self, event):
1264 head, tail, chars, lines = self.get_region()
1265 for pos in range(len(lines)):
1266 line = lines[pos]
1267 if line:
1268 raw, effective = classifyws(line, self.tabwidth)
1269 effective = effective + self.indentwidth
1270 lines[pos] = self._make_blanks(effective) + line[raw:]
1271 self.set_region(head, tail, chars, lines)
1272 return "break"
1273
1274 def dedent_region_event(self, event):
1275 head, tail, chars, lines = self.get_region()
1276 for pos in range(len(lines)):
1277 line = lines[pos]
1278 if line:
1279 raw, effective = classifyws(line, self.tabwidth)
1280 effective = max(effective - self.indentwidth, 0)
1281 lines[pos] = self._make_blanks(effective) + line[raw:]
1282 self.set_region(head, tail, chars, lines)
1283 return "break"
1284
1285 def comment_region_event(self, event):
1286 head, tail, chars, lines = self.get_region()
1287 for pos in range(len(lines) - 1):
1288 line = lines[pos]
1289 lines[pos] = '##' + line
1290 self.set_region(head, tail, chars, lines)
1291
1292 def uncomment_region_event(self, event):
1293 head, tail, chars, lines = self.get_region()
1294 for pos in range(len(lines)):
1295 line = lines[pos]
1296 if not line:
1297 continue
1298 if line[:2] == '##':
1299 line = line[2:]
1300 elif line[:1] == '#':
1301 line = line[1:]
1302 lines[pos] = line
1303 self.set_region(head, tail, chars, lines)
1304
1305 def tabify_region_event(self, event):
1306 head, tail, chars, lines = self.get_region()
1307 tabwidth = self._asktabwidth()
1308 for pos in range(len(lines)):
1309 line = lines[pos]
1310 if line:
1311 raw, effective = classifyws(line, tabwidth)
1312 ntabs, nspaces = divmod(effective, tabwidth)
1313 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1314 self.set_region(head, tail, chars, lines)
1315
1316 def untabify_region_event(self, event):
1317 head, tail, chars, lines = self.get_region()
1318 tabwidth = self._asktabwidth()
1319 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001320 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001321 self.set_region(head, tail, chars, lines)
1322
1323 def toggle_tabs_event(self, event):
1324 if self.askyesno(
1325 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001326 "Turn tabs " + ("on", "off")[self.usetabs] +
1327 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001328 ("will be", "remains at")[self.usetabs] + " 8." +
1329 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001330 parent=self.text):
1331 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001332 # Try to prevent inconsistent indentation.
1333 # User must change indent width manually after using tabs.
1334 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001335 return "break"
1336
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001337 # XXX this isn't bound to anything -- see tabwidth comments
1338## def change_tabwidth_event(self, event):
1339## new = self._asktabwidth()
1340## if new != self.tabwidth:
1341## self.tabwidth = new
1342## self.set_indentation_params(0, guess=0)
1343## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001344
1345 def change_indentwidth_event(self, event):
1346 new = self.askinteger(
1347 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001348 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001349 parent=self.text,
1350 initialvalue=self.indentwidth,
1351 minvalue=2,
1352 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001353 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001354 self.indentwidth = new
1355 return "break"
1356
1357 def get_region(self):
1358 text = self.text
1359 first, last = self.get_selection_indices()
1360 if first and last:
1361 head = text.index(first + " linestart")
1362 tail = text.index(last + "-1c lineend +1c")
1363 else:
1364 head = text.index("insert linestart")
1365 tail = text.index("insert lineend +1c")
1366 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001367 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001368 return head, tail, chars, lines
1369
1370 def set_region(self, head, tail, chars, lines):
1371 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001372 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001373 if newchars == chars:
1374 text.bell()
1375 return
1376 text.tag_remove("sel", "1.0", "end")
1377 text.mark_set("insert", head)
1378 text.undo_block_start()
1379 text.delete(head, tail)
1380 text.insert(head, newchars)
1381 text.undo_block_stop()
1382 text.tag_add("sel", head, "insert")
1383
1384 # Make string that displays as n leading blanks.
1385
1386 def _make_blanks(self, n):
1387 if self.usetabs:
1388 ntabs, nspaces = divmod(n, self.tabwidth)
1389 return '\t' * ntabs + ' ' * nspaces
1390 else:
1391 return ' ' * n
1392
1393 # Delete from beginning of line to insert point, then reinsert
1394 # column logical (meaning use tabs if appropriate) spaces.
1395
1396 def reindent_to(self, column):
1397 text = self.text
1398 text.undo_block_start()
1399 if text.compare("insert linestart", "!=", "insert"):
1400 text.delete("insert linestart", "insert")
1401 if column:
1402 text.insert("insert", self._make_blanks(column))
1403 text.undo_block_stop()
1404
1405 def _asktabwidth(self):
1406 return self.askinteger(
1407 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001408 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001409 parent=self.text,
1410 initialvalue=self.indentwidth,
1411 minvalue=2,
1412 maxvalue=16) or self.tabwidth
1413
1414 # Guess indentwidth from text content.
1415 # Return guessed indentwidth. This should not be believed unless
1416 # it's in a reasonable range (e.g., it will be 0 if no indented
1417 # blocks are found).
1418
1419 def guess_indent(self):
1420 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1421 if opener and indented:
1422 raw, indentsmall = classifyws(opener, self.tabwidth)
1423 raw, indentlarge = classifyws(indented, self.tabwidth)
1424 else:
1425 indentsmall = indentlarge = 0
1426 return indentlarge - indentsmall
1427
1428# "line.col" -> line, as an int
1429def index2line(index):
1430 return int(float(index))
1431
1432# Look at the leading whitespace in s.
1433# Return pair (# of leading ws characters,
1434# effective # of leading blanks after expanding
1435# tabs to width tabwidth)
1436
1437def classifyws(s, tabwidth):
1438 raw = effective = 0
1439 for ch in s:
1440 if ch == ' ':
1441 raw = raw + 1
1442 effective = effective + 1
1443 elif ch == '\t':
1444 raw = raw + 1
1445 effective = (effective // tabwidth + 1) * tabwidth
1446 else:
1447 break
1448 return raw, effective
1449
1450import tokenize
1451_tokenize = tokenize
1452del tokenize
1453
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001454class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001455
1456 # .run() chews over the Text widget, looking for a block opener
1457 # and the stmt following it. Returns a pair,
1458 # (line containing block opener, line containing stmt)
1459 # Either or both may be None.
1460
1461 def __init__(self, text, tabwidth):
1462 self.text = text
1463 self.tabwidth = tabwidth
1464 self.i = self.finished = 0
1465 self.blkopenline = self.indentedline = None
1466
1467 def readline(self):
1468 if self.finished:
1469 return ""
1470 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001471 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001472 if self.text.compare(mark, ">=", "end"):
1473 return ""
1474 return self.text.get(mark, mark + " lineend+1c")
1475
1476 def tokeneater(self, type, token, start, end, line,
1477 INDENT=_tokenize.INDENT,
1478 NAME=_tokenize.NAME,
1479 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1480 if self.finished:
1481 pass
1482 elif type == NAME and token in OPENERS:
1483 self.blkopenline = line
1484 elif type == INDENT and self.blkopenline:
1485 self.indentedline = line
1486 self.finished = 1
1487
1488 def run(self):
1489 save_tabsize = _tokenize.tabsize
1490 _tokenize.tabsize = self.tabwidth
1491 try:
1492 try:
Trent Nelson428de652008-03-18 22:41:35 +00001493 tokens = _tokenize.generate_tokens(self.readline)
1494 for token in tokens:
1495 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001496 except _tokenize.TokenError:
1497 # since we cut off the tokenizer early, we can trigger
1498 # spurious errors
1499 pass
1500 finally:
1501 _tokenize.tabsize = save_tabsize
1502 return self.blkopenline, self.indentedline
1503
1504### end autoindent code ###
1505
David Scherer7aced172000-08-15 01:13:23 +00001506def prepstr(s):
1507 # Helper to extract the underscore from a string, e.g.
1508 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001509 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001510 if i >= 0:
1511 s = s[:i] + s[i+1:]
1512 return i, s
1513
1514
1515keynames = {
1516 'bracketleft': '[',
1517 'bracketright': ']',
1518 'slash': '/',
1519}
1520
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001521def get_accelerator(keydefs, eventname):
1522 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001523 if not keylist:
1524 return ""
1525 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001526 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001527 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1528 s = re.sub("Key-", "", s)
1529 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1530 s = re.sub("Control-", "Ctrl-", s)
1531 s = re.sub("-", "+", s)
1532 s = re.sub("><", " ", s)
1533 s = re.sub("<", "", s)
1534 s = re.sub(">", "", s)
1535 return s
1536
1537
1538def fixwordbreaks(root):
1539 # Make sure that Tk's double-click and next/previous word
1540 # operations use our definition of a word (i.e. an identifier)
1541 tk = root.tk
1542 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1543 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1544 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1545
1546
1547def test():
1548 root = Tk()
1549 fixwordbreaks(root)
1550 root.withdraw()
1551 if sys.argv[1:]:
1552 filename = sys.argv[1]
1553 else:
1554 filename = None
1555 edit = EditorWindow(root=root, filename=filename)
1556 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001557 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001558 root.mainloop()
1559 root.destroy()
1560
1561if __name__ == '__main__':
1562 test()