blob: a634962375009c1f59edbff39b521320672020e5 [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
Georg Brandl14fc4272008-05-17 18:39:55 +00006from tkinter import *
7import tkinter.simpledialog as tkSimpleDialog
8import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +00009import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000010import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000011
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000012from idlelib.MultiCall import MultiCallCreator
13from idlelib import idlever
14from idlelib import WindowList
15from idlelib import SearchDialog
16from idlelib import GrepDialog
17from idlelib import ReplaceDialog
18from idlelib import PyParse
19from idlelib.configHandler import idleConf
20from idlelib import aboutDialog, textView, configDialog
21from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000022
23# The default tab setting for a Text widget, in average-width characters.
24TK_TABWIDTH_DEFAULT = 8
25
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000026def _sphinx_version():
27 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
28 major, minor, micro, level, serial = sys.version_info
29 release = '%s%s' % (major, minor)
30 if micro:
Benjamin Petersonb48f6342009-06-22 19:36:31 +000031 release += '%s' % (micro,)
32 if level == 'candidate':
33 release += 'rc%s' % (serial,)
34 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000035 release += '%s%s' % (level[0], serial)
36 return release
37
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000038def _find_module(fullname, path=None):
39 """Version of imp.find_module() that handles hierarchical module names"""
40
41 file = None
42 for tgt in fullname.split('.'):
43 if file is not None:
44 file.close() # close intermediate files
45 (file, filename, descr) = imp.find_module(tgt, path)
46 if descr[2] == imp.PY_SOURCE:
47 break # find but not load the source file
48 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000049 try:
50 path = module.__path__
51 except AttributeError:
Kurt B. Kaiserad667422007-08-23 01:06:15 +000052 raise ImportError('No source for module ' + module.__name__)
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000053 return file, filename, descr
54
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000055class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000056 from idlelib.Percolator import Percolator
57 from idlelib.ColorDelegator import ColorDelegator
58 from idlelib.UndoDelegator import UndoDelegator
59 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
60 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000061 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000062 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000063
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000064 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000065
66 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000067 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000068 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000069 if sys.platform.count('linux'):
70 # look for html docs in a couple of standard places
71 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
72 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
73 dochome = '/var/www/html/python/index.html'
74 else:
75 basepath = '/usr/share/doc/' # standard location
76 dochome = os.path.join(basepath, pyver,
77 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000078 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000079 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000080 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000081 if os.path.isfile(chmfile):
82 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +000083 elif macosxSupport.runningAsOSXApp():
84 # documentation is stored inside the python framework
85 dochome = os.path.join(sys.prefix,
86 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000087 dochome = os.path.normpath(dochome)
88 if os.path.isfile(dochome):
89 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +000090 if sys.platform == 'darwin':
91 # Safari requires real file:-URLs
92 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000093 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +000094 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +000095 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000096 self.flist = flist
97 root = root or flist.root
98 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +000099 try:
100 sys.ps1
101 except AttributeError:
102 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000103 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000104 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000105 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000106 self.tkinter_vars = flist.vars
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000107 #self.top.instance_dict makes flist.inversedict avalable to
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000108 #configDialog.py so it can access all EditorWindow instaces
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000109 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000110 else:
111 self.tkinter_vars = {} # keys: Tkinter event names
112 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000113 self.top.instance_dict = {}
114 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000115 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000116 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000117 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000118 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000119 text_options = {
120 'name': 'text',
121 'padx': 5,
122 'wrap': 'none',
123 'width': self.width,
124 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
125 if TkVersion >= 8.5:
126 # Starting with tk 8.5 we have to set the new tabstyle option
127 # to 'wordprocessor' to achieve the same display of tabs as in
128 # older tk versions.
129 text_options['tabstyle'] = 'wordprocessor'
130 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000131 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000132
133 self.createmenubar()
134 self.apply_bindings()
135
136 self.top.protocol("WM_DELETE_WINDOW", self.close)
137 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000138 if macosxSupport.runningAsOSXApp():
139 # Command-W on editorwindows doesn't work without this.
140 text.bind('<<close-window>>', self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000141 text.bind("<<cut>>", self.cut)
142 text.bind("<<copy>>", self.copy)
143 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000144 text.bind("<<center-insert>>", self.center_insert_event)
145 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000146 text.bind("<<python-docs>>", self.python_docs)
147 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000148 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000149 text.bind("<<open-module>>", self.open_module)
150 text.bind("<<do-nothing>>", lambda event: "break")
151 text.bind("<<select-all>>", self.select_all)
152 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000153 text.bind("<<find>>", self.find_event)
154 text.bind("<<find-again>>", self.find_again_event)
155 text.bind("<<find-in-files>>", self.find_in_files_event)
156 text.bind("<<find-selection>>", self.find_selection_event)
157 text.bind("<<replace>>", self.replace_event)
158 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000159 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000160 text.bind("<<smart-backspace>>",self.smart_backspace_event)
161 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
162 text.bind("<<smart-indent>>",self.smart_indent_event)
163 text.bind("<<indent-region>>",self.indent_region_event)
164 text.bind("<<dedent-region>>",self.dedent_region_event)
165 text.bind("<<comment-region>>",self.comment_region_event)
166 text.bind("<<uncomment-region>>",self.uncomment_region_event)
167 text.bind("<<tabify-region>>",self.tabify_region_event)
168 text.bind("<<untabify-region>>",self.untabify_region_event)
169 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
170 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000171 text.bind("<Left>", self.move_at_edge_if_selection(0))
172 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000173 text.bind("<<del-word-left>>", self.del_word_left)
174 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000175 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000176
David Scherer7aced172000-08-15 01:13:23 +0000177 if flist:
178 flist.inversedict[self] = key
179 if key:
180 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000181 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000182 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
183 text.bind("<<open-class-browser>>", self.open_class_browser)
184 text.bind("<<open-path-browser>>", self.open_path_browser)
185
Steven M. Gava898a3652001-10-07 11:10:44 +0000186 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000187 vbar['command'] = text.yview
188 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000189 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000190 fontWeight = 'normal'
191 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000192 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000193 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
194 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
195 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000196 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
197 text.pack(side=TOP, fill=BOTH, expand=1)
198 text.focus_set()
199
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000200 # usetabs true -> literal tab characters are used by indent and
201 # dedent cmds, possibly mixed with spaces if
202 # indentwidth is not a multiple of tabwidth,
203 # which will cause Tabnanny to nag!
204 # false -> tab characters are converted to spaces by indent
205 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000206 # Although use-spaces=0 can be configured manually in config-main.def,
207 # configuration of tabs v. spaces is not supported in the configuration
208 # dialog. IDLE promotes the preferred Python indentation: use spaces!
209 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
210 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000211
212 # tabwidth is the display width of a literal tab character.
213 # CAUTION: telling Tk to use anything other than its default
214 # tab setting causes it to use an entirely different tabbing algorithm,
215 # treating tab stops as fixed distances from the left margin.
216 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000217 self.tabwidth = 8 # must remain 8 until Tk is fixed.
218
219 # indentwidth is the number of screen characters per indent level.
220 # The recommended Python indentation is four spaces.
221 self.indentwidth = self.tabwidth
222 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000223
224 # If context_use_ps1 is true, parsing searches back for a ps1 line;
225 # else searches for a popular (if, def, ...) Python stmt.
226 self.context_use_ps1 = False
227
228 # When searching backwards for a reliable place to begin parsing,
229 # first start num_context_lines[0] lines back, then
230 # num_context_lines[1] lines back if that didn't work, and so on.
231 # The last value should be huge (larger than the # of lines in a
232 # conceivable file).
233 # Making the initial values larger slows things down more often.
234 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000235 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000236 self.undo = undo = self.UndoDelegator()
237 per.insertfilter(undo)
238 text.undo_block_start = undo.undo_block_start
239 text.undo_block_stop = undo.undo_block_stop
240 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000241 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000242 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000243 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000244 self.good_load = False
245 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000246 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000247 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000248 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000249 if io.loadfile(filename):
250 self.good_load = True
251 is_py_src = self.ispythonsource(filename)
252 self.set_indentation_params(is_py_src)
253 if is_py_src:
254 self.color = color = self.ColorDelegator()
255 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000256 else:
257 io.set_filename(filename)
Christian Heimesa156e092008-02-16 07:38:31 +0000258 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000259 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000260 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000261 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000262 menu = self.menudict.get('windows')
263 if menu:
264 end = menu.index("end")
265 if end is None:
266 end = -1
267 if end >= 0:
268 menu.add_separator()
269 end = end + 1
270 self.wmenu_end = end
271 WindowList.register_callback(self.postwindowsmenu)
272
273 # Some abstractions so IDLE extensions are cross-IDE
274 self.askyesno = tkMessageBox.askyesno
275 self.askinteger = tkSimpleDialog.askinteger
276 self.showerror = tkMessageBox.showerror
277
Martin v. Löwis307021f2005-11-27 16:59:04 +0000278 def _filename_to_unicode(self, filename):
279 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000280 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000281 return filename
282 else:
283 try:
284 return filename.decode(self.filesystemencoding)
285 except UnicodeDecodeError:
286 # XXX
287 try:
288 return filename.decode(self.encoding)
289 except UnicodeDecodeError:
290 # byte-to-byte conversion
291 return filename.decode('iso8859-1')
292
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000293 def new_callback(self, event):
294 dirname, basename = self.io.defaultfilename()
295 self.flist.new(dirname)
296 return "break"
297
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000298 def home_callback(self, event):
299 if (event.state & 12) != 0 and event.keysym == "Home":
300 # state&1==shift, state&4==control, state&8==alt
301 return # <Modifier-Home>; fall back to class binding
302
303 if self.text.index("iomark") and \
304 self.text.compare("iomark", "<=", "insert lineend") and \
305 self.text.compare("insert linestart", "<=", "iomark"):
306 insertpt = int(self.text.index("iomark").split(".")[1])
307 else:
308 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000309 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000310 if line[insertpt] not in (' ','\t'):
311 break
312 else:
313 insertpt=len(line)
314
315 lineat = int(self.text.index("insert").split('.')[1])
316
317 if insertpt == lineat:
318 insertpt = 0
319
320 dest = "insert linestart+"+str(insertpt)+"c"
321
322 if (event.state&1) == 0:
323 # shift not pressed
324 self.text.tag_remove("sel", "1.0", "end")
325 else:
326 if not self.text.index("sel.first"):
327 self.text.mark_set("anchor","insert")
328
329 first = self.text.index(dest)
330 last = self.text.index("anchor")
331
332 if self.text.compare(first,">",last):
333 first,last = last,first
334
335 self.text.tag_remove("sel", "1.0", "end")
336 self.text.tag_add("sel", first, last)
337
338 self.text.mark_set("insert", dest)
339 self.text.see("insert")
340 return "break"
341
David Scherer7aced172000-08-15 01:13:23 +0000342 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000343 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000344 if macosxSupport.runningAsOSXApp():
345 # Insert some padding to avoid obscuring some of the statusbar
346 # by the resize widget.
347 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000348 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
349 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
350 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000351 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
352 self.text.event_add("<<set-line-and-column>>",
353 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000354 self.text.after_idle(self.set_line_and_column)
355
356 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000357 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000358 self.status_bar.set_label('column', 'Col: %s' % column)
359 self.status_bar.set_label('line', 'Ln: %s' % line)
360
David Scherer7aced172000-08-15 01:13:23 +0000361 menu_specs = [
362 ("file", "_File"),
363 ("edit", "_Edit"),
364 ("format", "F_ormat"),
365 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000366 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000367 ("windows", "_Windows"),
368 ("help", "_Help"),
369 ]
370
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000371 if macosxSupport.runningAsOSXApp():
372 del menu_specs[-3]
373 menu_specs[-2] = ("windows", "_Window")
374
375
David Scherer7aced172000-08-15 01:13:23 +0000376 def createmenubar(self):
377 mbar = self.menubar
378 self.menudict = menudict = {}
379 for name, label in self.menu_specs:
380 underline, label = prepstr(label)
381 menudict[name] = menu = Menu(mbar, name=name)
382 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren827822e2009-02-12 15:01:44 +0000383 if macosxSupport.runningAsOSXApp():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000384 # Insert the application menu
385 menudict['application'] = menu = Menu(mbar, name='apple')
386 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000387 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000388 self.recent_files_menu = Menu(self.menubar)
389 self.menudict['file'].insert_cascade(3, label='Recent Files',
390 underline=0,
391 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000392 self.base_helpmenu_length = self.menudict['help'].index(END)
393 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000394
395 def postwindowsmenu(self):
396 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000397 menu = self.menudict['windows']
398 end = menu.index("end")
399 if end is None:
400 end = -1
401 if end > self.wmenu_end:
402 menu.delete(self.wmenu_end+1, end)
403 WindowList.add_windows_to_menu(menu)
404
405 rmenu = None
406
407 def right_menu_event(self, event):
408 self.text.tag_remove("sel", "1.0", "end")
409 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
410 if not self.rmenu:
411 self.make_rmenu()
412 rmenu = self.rmenu
413 self.event = event
414 iswin = sys.platform[:3] == 'win'
415 if iswin:
416 self.text.config(cursor="arrow")
417 rmenu.tk_popup(event.x_root, event.y_root)
418 if iswin:
419 self.text.config(cursor="ibeam")
420
421 rmenu_specs = [
422 # ("Label", "<<virtual-event>>"), ...
423 ("Close", "<<close-window>>"), # Example
424 ]
425
426 def make_rmenu(self):
427 rmenu = Menu(self.text, tearoff=0)
428 for label, eventname in self.rmenu_specs:
429 def command(text=self.text, eventname=eventname):
430 text.event_generate(eventname)
431 rmenu.add_command(label=label, command=command)
432 self.rmenu = rmenu
433
434 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000435 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000436
Steven M. Gava3b55a892001-11-21 05:56:26 +0000437 def config_dialog(self, event=None):
438 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000439
David Scherer7aced172000-08-15 01:13:23 +0000440 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000441 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000442 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000443
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000444 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000445 if sys.platform[:3] == 'win':
Steven M. Gava931625d2002-04-22 00:38:26 +0000446 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000447 else:
448 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000449 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000450
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000451 def cut(self,event):
452 self.text.event_generate("<<Cut>>")
453 return "break"
454
455 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000456 if not self.text.tag_ranges("sel"):
457 # There is no selection, so do nothing and maybe interrupt.
458 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000459 self.text.event_generate("<<Copy>>")
460 return "break"
461
462 def paste(self,event):
463 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000464 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000465 return "break"
466
David Scherer7aced172000-08-15 01:13:23 +0000467 def select_all(self, event=None):
468 self.text.tag_add("sel", "1.0", "end-1c")
469 self.text.mark_set("insert", "1.0")
470 self.text.see("insert")
471 return "break"
472
473 def remove_selection(self, event=None):
474 self.text.tag_remove("sel", "1.0", "end")
475 self.text.see("insert")
476
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000477 def move_at_edge_if_selection(self, edge_index):
478 """Cursor move begins at start or end of selection
479
480 When a left/right cursor key is pressed create and return to Tkinter a
481 function which causes a cursor move from the associated edge of the
482 selection.
483
484 """
485 self_text_index = self.text.index
486 self_text_mark_set = self.text.mark_set
487 edges_table = ("sel.first+1c", "sel.last-1c")
488 def move_at_edge(event):
489 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
490 try:
491 self_text_index("sel.first")
492 self_text_mark_set("insert", edges_table[edge_index])
493 except TclError:
494 pass
495 return move_at_edge
496
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000497 def del_word_left(self, event):
498 self.text.event_generate('<Meta-Delete>')
499 return "break"
500
501 def del_word_right(self, event):
502 self.text.event_generate('<Meta-d>')
503 return "break"
504
Steven M. Gavac5976402002-01-04 03:06:08 +0000505 def find_event(self, event):
506 SearchDialog.find(self.text)
507 return "break"
508
509 def find_again_event(self, event):
510 SearchDialog.find_again(self.text)
511 return "break"
512
513 def find_selection_event(self, event):
514 SearchDialog.find_selection(self.text)
515 return "break"
516
517 def find_in_files_event(self, event):
518 GrepDialog.grep(self.text, self.io, self.flist)
519 return "break"
520
521 def replace_event(self, event):
522 ReplaceDialog.replace(self.text)
523 return "break"
524
525 def goto_line_event(self, event):
526 text = self.text
527 lineno = tkSimpleDialog.askinteger("Goto",
528 "Go to line number:",parent=text)
529 if lineno is None:
530 return "break"
531 if lineno <= 0:
532 text.bell()
533 return "break"
534 text.mark_set("insert", "%d.0" % lineno)
535 text.see("insert")
536
David Scherer7aced172000-08-15 01:13:23 +0000537 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000538 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000539 try:
540 name = self.text.get("sel.first", "sel.last")
541 except TclError:
542 name = ""
543 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000544 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000545 name = tkSimpleDialog.askstring("Module",
546 "Enter the name of a Python module\n"
547 "to search on sys.path and open:",
548 parent=self.text, initialvalue=name)
549 if name:
550 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000551 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000552 return
David Scherer7aced172000-08-15 01:13:23 +0000553 # XXX Ought to insert current file's directory in front of path
554 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000555 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000556 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000557 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
558 return
559 if type != imp.PY_SOURCE:
560 tkMessageBox.showerror("Unsupported type",
561 "%s is not a source module" % name, parent=self.text)
562 return
563 if f:
564 f.close()
565 if self.flist:
566 self.flist.open(file)
567 else:
568 self.io.loadfile(file)
569
570 def open_class_browser(self, event=None):
571 filename = self.io.filename
572 if not filename:
573 tkMessageBox.showerror(
574 "No filename",
575 "This buffer has no associated filename",
576 master=self.text)
577 self.text.focus_set()
578 return None
579 head, tail = os.path.split(filename)
580 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000581 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000582 ClassBrowser.ClassBrowser(self.flist, base, [head])
583
584 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000585 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000586 PathBrowser.PathBrowser(self.flist)
587
588 def gotoline(self, lineno):
589 if lineno is not None and lineno > 0:
590 self.text.mark_set("insert", "%d.0" % lineno)
591 self.text.tag_remove("sel", "1.0", "end")
592 self.text.tag_add("sel", "insert", "insert +1l")
593 self.center()
594
595 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000596 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000597 return True
David Scherer7aced172000-08-15 01:13:23 +0000598 base, ext = os.path.splitext(os.path.basename(filename))
599 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000600 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000601 line = self.text.get('1.0', '1.0 lineend')
602 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000603
604 def close_hook(self):
605 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000606 self.flist.unregister_maybe_terminate(self)
607 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000608
609 def set_close_hook(self, close_hook):
610 self.close_hook = close_hook
611
612 def filename_change_hook(self):
613 if self.flist:
614 self.flist.filename_changed_edit(self)
615 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000616 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000617 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000618
Christian Heimesa156e092008-02-16 07:38:31 +0000619 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000620 if self.color:
621 return
Christian Heimesa156e092008-02-16 07:38:31 +0000622 if self.ispythonsource(self.io.filename):
623 self.color = self.ColorDelegator()
624 # can add more colorizers here...
625 if self.color:
626 self.per.removefilter(self.undo)
627 self.per.insertfilter(self.color)
628 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000629
Christian Heimesa156e092008-02-16 07:38:31 +0000630 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000631 if not self.color:
632 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000633 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000634 self.per.removefilter(self.color)
635 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000636
Steven M. Gavab77d3432002-03-02 07:16:21 +0000637 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000638 "Update the colour theme"
639 # Called from self.filename_change_hook and from configDialog.py
640 self._rmcolorizer()
641 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000642 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000643 normal_colors = idleConf.GetHighlight(theme, 'normal')
644 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
645 select_colors = idleConf.GetHighlight(theme, 'hilite')
646 self.text.config(
647 foreground=normal_colors['foreground'],
648 background=normal_colors['background'],
649 insertbackground=cursor_color,
650 selectforeground=select_colors['foreground'],
651 selectbackground=select_colors['background'],
652 )
David Scherer7aced172000-08-15 01:13:23 +0000653
Guido van Rossum33d26892007-08-05 15:29:28 +0000654 IDENTCHARS = string.ascii_letters + string.digits + "_"
655
656 def colorize_syntax_error(self, text, pos):
657 text.tag_add("ERROR", pos)
658 char = text.get(pos)
659 if char and char in self.IDENTCHARS:
660 text.tag_add("ERROR", pos + " wordstart", pos)
661 if '\n' == text.get(pos): # error at line end
662 text.mark_set("insert", pos)
663 else:
664 text.mark_set("insert", pos + "+1c")
665 text.see(pos)
666
Steven M. Gavab1585412002-03-12 00:21:56 +0000667 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000668 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000669 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000670 fontWeight='normal'
671 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
672 fontWeight='bold'
673 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
674 idleConf.GetOption('main','EditorWindow','font-size'),
675 fontWeight))
676
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000677 def RemoveKeybindings(self):
678 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000679 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000680 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000681 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000682 self.text.event_delete(event, *keylist)
683 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000684 xkeydefs = idleConf.GetExtensionBindings(extensionName)
685 if xkeydefs:
686 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000687 self.text.event_delete(event, *keylist)
688
689 def ApplyKeybindings(self):
690 "Update the keybindings after they are changed"
691 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000692 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000693 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000694 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000695 xkeydefs = idleConf.GetExtensionBindings(extensionName)
696 if xkeydefs:
697 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000698 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000699 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000700 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000701 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000702 for item in menu[1]:
703 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000704 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000705 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000706 menu = self.menudict[menubarItem]
707 end = menu.index(END) + 1
708 for index in range(0, end):
709 if menu.type(index) == 'command':
710 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000711 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000712 itemName = menu.entrycget(index, 'label')
713 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000714 if menubarItem in menuEventDict:
715 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000716 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000717 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000718 accel = get_accelerator(keydefs, event)
719 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000720
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000721 def set_notabs_indentwidth(self):
722 "Update the indentwidth if changed and not using tabs in this window"
723 # Called from configDialog.py
724 if not self.usetabs:
725 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
726 type='int')
727
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000728 def reset_help_menu_entries(self):
729 "Update the additional help entries on the Help menu"
730 help_list = idleConf.GetAllExtraHelpSourcesList()
731 helpmenu = self.menudict['help']
732 # first delete the extra help entries, if any
733 helpmenu_length = helpmenu.index(END)
734 if helpmenu_length > self.base_helpmenu_length:
735 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
736 # then rebuild them
737 if help_list:
738 helpmenu.add_separator()
739 for entry in help_list:
740 cmd = self.__extra_help_callback(entry[1])
741 helpmenu.add_command(label=entry[0], command=cmd)
742 # and update the menu dictionary
743 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000744
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000745 def __extra_help_callback(self, helpfile):
746 "Create a callback with the helpfile value frozen at definition time"
747 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000748 if not helpfile.startswith(('www', 'http')):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000749 url = os.path.normpath(helpfile)
750 if sys.platform[:3] == 'win':
751 os.startfile(helpfile)
752 else:
753 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000754 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000755
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000756 def update_recent_files_list(self, new_file=None):
757 "Load and update the recent files list and menus"
758 rf_list = []
759 if os.path.exists(self.recent_files_path):
760 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000761 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000762 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000763 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000764 rf_list_file.close()
765 if new_file:
766 new_file = os.path.abspath(new_file) + '\n'
767 if new_file in rf_list:
768 rf_list.remove(new_file) # move to top
769 rf_list.insert(0, new_file)
770 # clean and save the recent files list
771 bad_paths = []
772 for path in rf_list:
773 if '\0' in path or not os.path.exists(path[0:-1]):
774 bad_paths.append(path)
775 rf_list = [path for path in rf_list if path not in bad_paths]
776 ulchars = "1234567890ABCDEFGHIJK"
777 rf_list = rf_list[0:len(ulchars)]
778 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000779 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000780 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000781 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000782 rf_file.close()
783 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000784 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000785 menu = instance.recent_files_menu
786 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000787 for i, file_name in enumerate(rf_list):
788 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000789 # make unicode string to display non-ASCII chars correctly
790 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000791 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000792 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000793 command=callback,
794 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000795
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000796 def __recent_file_callback(self, file_name):
797 def open_recent_file(fn_closure=file_name):
798 self.io.open(editFile=fn_closure)
799 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000800
David Scherer7aced172000-08-15 01:13:23 +0000801 def saved_change_hook(self):
802 short = self.short_title()
803 long = self.long_title()
804 if short and long:
805 title = short + " - " + long
806 elif short:
807 title = short
808 elif long:
809 title = long
810 else:
811 title = "Untitled"
812 icon = short or long or title
813 if not self.get_saved():
814 title = "*%s*" % title
815 icon = "*%s" % icon
816 self.top.wm_title(title)
817 self.top.wm_iconname(icon)
818
819 def get_saved(self):
820 return self.undo.get_saved()
821
822 def set_saved(self, flag):
823 self.undo.set_saved(flag)
824
825 def reset_undo(self):
826 self.undo.reset_undo()
827
828 def short_title(self):
829 filename = self.io.filename
830 if filename:
831 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000832 # return unicode string to display non-ASCII chars correctly
833 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000834
835 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000836 # return unicode string to display non-ASCII chars correctly
837 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000838
839 def center_insert_event(self, event):
840 self.center()
841
842 def center(self, mark="insert"):
843 text = self.text
844 top, bot = self.getwindowlines()
845 lineno = self.getlineno(mark)
846 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000847 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000848 text.yview(float(newtop))
849
850 def getwindowlines(self):
851 text = self.text
852 top = self.getlineno("@0,0")
853 bot = self.getlineno("@0,65535")
854 if top == bot and text.winfo_height() == 1:
855 # Geometry manager hasn't run yet
856 height = int(text['height'])
857 bot = top + height - 1
858 return top, bot
859
860 def getlineno(self, mark="insert"):
861 text = self.text
862 return int(float(text.index(mark)))
863
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000864 def get_geometry(self):
865 "Return (width, height, x, y)"
866 geom = self.top.wm_geometry()
867 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000868 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000869
David Scherer7aced172000-08-15 01:13:23 +0000870 def close_event(self, event):
871 self.close()
872
873 def maybesave(self):
874 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000875 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000876 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000877 self.top.deiconify()
878 self.top.lower()
879 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000880 return self.io.maybesave()
881
882 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000883 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000884 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000885 self._close()
886 return reply
887
888 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000889 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000890 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000891 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000892 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000893 self.io.close()
894 self.io = None
895 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000896 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000897 self.color.close(False)
898 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000899 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000900 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000901 self.per.close()
902 self.per = None
903 self.top.destroy()
904 if self.close_hook:
905 # unless override: unregister from flist, terminate if last window
906 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000907
908 def load_extensions(self):
909 self.extensions = {}
910 self.load_standard_extensions()
911
912 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000913 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000914 if hasattr(ins, "close"):
915 ins.close()
916 self.extensions = {}
917
918 def load_standard_extensions(self):
919 for name in self.get_standard_extension_names():
920 try:
921 self.load_extension(name)
922 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000923 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000924 traceback.print_exc()
925
926 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000927 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000928
929 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000930 try:
931 mod = __import__(name, globals(), locals(), [])
932 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000933 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000934 raise
David Scherer7aced172000-08-15 01:13:23 +0000935 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000936 keydefs = idleConf.GetExtensionBindings(name)
937 if hasattr(cls, "menudefs"):
938 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000939 ins = cls(self)
940 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000941 if keydefs:
942 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000943 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000944 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000945 while methodname[:1] == '<':
946 methodname = methodname[1:]
947 while methodname[-1:] == '>':
948 methodname = methodname[:-1]
949 methodname = methodname + "_event"
950 if hasattr(ins, methodname):
951 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000952
953 def apply_bindings(self, keydefs=None):
954 if keydefs is None:
955 keydefs = self.Bindings.default_keydefs
956 text = self.text
957 text.keydefs = keydefs
958 for event, keylist in keydefs.items():
959 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000960 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000961
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000962 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000963 """Add appropriate entries to the menus and submenus
964
965 Menus that are absent or None in self.menudict are ignored.
966 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000967 if menudefs is None:
968 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000969 if keydefs is None:
970 keydefs = self.Bindings.default_keydefs
971 menudict = self.menudict
972 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000973 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000974 menu = menudict.get(mname)
975 if not menu:
976 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000977 for entry in entrylist:
978 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000979 menu.add_separator()
980 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000981 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000982 checkbutton = (label[:1] == '!')
983 if checkbutton:
984 label = label[1:]
985 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000986 accelerator = get_accelerator(keydefs, eventname)
987 def command(text=text, eventname=eventname):
988 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000989 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000990 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000991 menu.add_checkbutton(label=label, underline=underline,
992 command=command, accelerator=accelerator,
993 variable=var)
994 else:
995 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000996 command=command,
997 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000998
999 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001000 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001001 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001002 value = var.get()
1003 return value
1004 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001005 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001006
1007 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001008 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001009 if var:
1010 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001011 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001012 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001013
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001014 def get_var_obj(self, name, vartype=None):
1015 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001016 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001017 # create a Tkinter variable object with self.text as master:
1018 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001019 return var
1020
1021 # Tk implementations of "virtual text methods" -- each platform
1022 # reusing IDLE's support code needs to define these for its GUI's
1023 # flavor of widget.
1024
1025 # Is character at text_index in a Python string? Return 0 for
1026 # "guaranteed no", true for anything else. This info is expensive
1027 # to compute ab initio, but is probably already known by the
1028 # platform's colorizer.
1029
1030 def is_char_in_string(self, text_index):
1031 if self.color:
1032 # Return true iff colorizer hasn't (re)gotten this far
1033 # yet, or the character is tagged as being in a string
1034 return self.text.tag_prevrange("TODO", text_index) or \
1035 "STRING" in self.text.tag_names(text_index)
1036 else:
1037 # The colorizer is missing: assume the worst
1038 return 1
1039
1040 # If a selection is defined in the text widget, return (start,
1041 # end) as Tkinter text indices, otherwise return (None, None)
1042 def get_selection_indices(self):
1043 try:
1044 first = self.text.index("sel.first")
1045 last = self.text.index("sel.last")
1046 return first, last
1047 except TclError:
1048 return None, None
1049
1050 # Return the text widget's current view of what a tab stop means
1051 # (equivalent width in spaces).
1052
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001053 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001054 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1055 return int(current)
1056
1057 # Set the text widget's current view of what a tab stop means.
1058
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001059 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001060 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001061 if self.get_tk_tabwidth() != newtabwidth:
1062 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001063 pixels = text.tk.call("font", "measure", text["font"],
1064 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001065 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001066 text.configure(tabs=pixels)
1067
Guido van Rossum33d26892007-08-05 15:29:28 +00001068### begin autoindent code ### (configuration was moved to beginning of class)
1069
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001070 def set_indentation_params(self, is_py_src, guess=True):
1071 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001072 i = self.guess_indent()
1073 if 2 <= i <= 8:
1074 self.indentwidth = i
1075 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001076 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001077 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001078
1079 def smart_backspace_event(self, event):
1080 text = self.text
1081 first, last = self.get_selection_indices()
1082 if first and last:
1083 text.delete(first, last)
1084 text.mark_set("insert", first)
1085 return "break"
1086 # Delete whitespace left, until hitting a real char or closest
1087 # preceding virtual tab stop.
1088 chars = text.get("insert linestart", "insert")
1089 if chars == '':
1090 if text.compare("insert", ">", "1.0"):
1091 # easy: delete preceding newline
1092 text.delete("insert-1c")
1093 else:
1094 text.bell() # at start of buffer
1095 return "break"
1096 if chars[-1] not in " \t":
1097 # easy: delete preceding real char
1098 text.delete("insert-1c")
1099 return "break"
1100 # Ick. It may require *inserting* spaces if we back up over a
1101 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001102 tabwidth = self.tabwidth
1103 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001104 assert have > 0
1105 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001106 # Debug prompt is multilined....
1107 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001108 ncharsdeleted = 0
1109 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001110 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001111 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001112 chars = chars[:-1]
1113 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001114 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001115 if have <= want or chars[-1] not in " \t":
1116 break
1117 text.undo_block_start()
1118 text.delete("insert-%dc" % ncharsdeleted, "insert")
1119 if have < want:
1120 text.insert("insert", ' ' * (want - have))
1121 text.undo_block_stop()
1122 return "break"
1123
1124 def smart_indent_event(self, event):
1125 # if intraline selection:
1126 # delete it
1127 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001128 # do indent-region
1129 # else:
1130 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001131 text = self.text
1132 first, last = self.get_selection_indices()
1133 text.undo_block_start()
1134 try:
1135 if first and last:
1136 if index2line(first) != index2line(last):
1137 return self.indent_region_event(event)
1138 text.delete(first, last)
1139 text.mark_set("insert", first)
1140 prefix = text.get("insert linestart", "insert")
1141 raw, effective = classifyws(prefix, self.tabwidth)
1142 if raw == len(prefix):
1143 # only whitespace to the left
1144 self.reindent_to(effective + self.indentwidth)
1145 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001146 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001147 if self.usetabs:
1148 pad = '\t'
1149 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001150 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001151 n = self.indentwidth
1152 pad = ' ' * (n - effective % n)
1153 text.insert("insert", pad)
1154 text.see("insert")
1155 return "break"
1156 finally:
1157 text.undo_block_stop()
1158
1159 def newline_and_indent_event(self, event):
1160 text = self.text
1161 first, last = self.get_selection_indices()
1162 text.undo_block_start()
1163 try:
1164 if first and last:
1165 text.delete(first, last)
1166 text.mark_set("insert", first)
1167 line = text.get("insert linestart", "insert")
1168 i, n = 0, len(line)
1169 while i < n and line[i] in " \t":
1170 i = i+1
1171 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001172 # the cursor is in or at leading indentation in a continuation
1173 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001174 text.insert("insert linestart", '\n')
1175 return "break"
1176 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001177 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001178 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001179 last_line_of_prompt = sys.ps1.split('\n')[-1]
1180 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001181 line = line[:-1]
1182 i = i+1
1183 if i:
1184 text.delete("insert - %d chars" % i, "insert")
1185 # strip whitespace after insert point
1186 while text.get("insert") in " \t":
1187 text.delete("insert")
1188 # start new line
1189 text.insert("insert", '\n')
1190
1191 # adjust indentation for continuations and block
1192 # open/close first need to find the last stmt
1193 lno = index2line(text.index('insert'))
1194 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001195 if not self.context_use_ps1:
1196 for context in self.num_context_lines:
1197 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001198 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001199 rawtext = text.get(startatindex, "insert")
1200 y.set_str(rawtext)
1201 bod = y.find_good_parse_start(
1202 self.context_use_ps1,
1203 self._build_char_in_string_func(startatindex))
1204 if bod is not None or startat == 1:
1205 break
1206 y.set_lo(bod or 0)
1207 else:
1208 r = text.tag_prevrange("console", "insert")
1209 if r:
1210 startatindex = r[1]
1211 else:
1212 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001213 rawtext = text.get(startatindex, "insert")
1214 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001215 y.set_lo(0)
1216
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001217 c = y.get_continuation_type()
1218 if c != PyParse.C_NONE:
1219 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001220 if c == PyParse.C_STRING_FIRST_LINE:
1221 # after the first line of a string; do not indent at all
1222 pass
1223 elif c == PyParse.C_STRING_NEXT_LINES:
1224 # inside a string which started before this line;
1225 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001226 text.insert("insert", indent)
1227 elif c == PyParse.C_BRACKET:
1228 # line up with the first (if any) element of the
1229 # last open bracket structure; else indent one
1230 # level beyond the indent of the line with the
1231 # last open bracket
1232 self.reindent_to(y.compute_bracket_indent())
1233 elif c == PyParse.C_BACKSLASH:
1234 # if more than one line in this stmt already, just
1235 # mimic the current indent; else if initial line
1236 # has a start on an assignment stmt, indent to
1237 # beyond leftmost =; else to beyond first chunk of
1238 # non-whitespace on initial line
1239 if y.get_num_lines_in_stmt() > 1:
1240 text.insert("insert", indent)
1241 else:
1242 self.reindent_to(y.compute_backslash_indent())
1243 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001244 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001245 return "break"
1246
1247 # This line starts a brand new stmt; indent relative to
1248 # indentation of initial line of closest preceding
1249 # interesting stmt.
1250 indent = y.get_base_indent_string()
1251 text.insert("insert", indent)
1252 if y.is_block_opener():
1253 self.smart_indent_event(event)
1254 elif indent and y.is_block_closer():
1255 self.smart_backspace_event(event)
1256 return "break"
1257 finally:
1258 text.see("insert")
1259 text.undo_block_stop()
1260
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001261 # Our editwin provides a is_char_in_string function that works
1262 # with a Tk text index, but PyParse only knows about offsets into
1263 # a string. This builds a function for PyParse that accepts an
1264 # offset.
1265
1266 def _build_char_in_string_func(self, startindex):
1267 def inner(offset, _startindex=startindex,
1268 _icis=self.is_char_in_string):
1269 return _icis(_startindex + "+%dc" % offset)
1270 return inner
1271
1272 def indent_region_event(self, event):
1273 head, tail, chars, lines = self.get_region()
1274 for pos in range(len(lines)):
1275 line = lines[pos]
1276 if line:
1277 raw, effective = classifyws(line, self.tabwidth)
1278 effective = effective + self.indentwidth
1279 lines[pos] = self._make_blanks(effective) + line[raw:]
1280 self.set_region(head, tail, chars, lines)
1281 return "break"
1282
1283 def dedent_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 = max(effective - self.indentwidth, 0)
1290 lines[pos] = self._make_blanks(effective) + line[raw:]
1291 self.set_region(head, tail, chars, lines)
1292 return "break"
1293
1294 def comment_region_event(self, event):
1295 head, tail, chars, lines = self.get_region()
1296 for pos in range(len(lines) - 1):
1297 line = lines[pos]
1298 lines[pos] = '##' + line
1299 self.set_region(head, tail, chars, lines)
1300
1301 def uncomment_region_event(self, event):
1302 head, tail, chars, lines = self.get_region()
1303 for pos in range(len(lines)):
1304 line = lines[pos]
1305 if not line:
1306 continue
1307 if line[:2] == '##':
1308 line = line[2:]
1309 elif line[:1] == '#':
1310 line = line[1:]
1311 lines[pos] = line
1312 self.set_region(head, tail, chars, lines)
1313
1314 def tabify_region_event(self, event):
1315 head, tail, chars, lines = self.get_region()
1316 tabwidth = self._asktabwidth()
1317 for pos in range(len(lines)):
1318 line = lines[pos]
1319 if line:
1320 raw, effective = classifyws(line, tabwidth)
1321 ntabs, nspaces = divmod(effective, tabwidth)
1322 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1323 self.set_region(head, tail, chars, lines)
1324
1325 def untabify_region_event(self, event):
1326 head, tail, chars, lines = self.get_region()
1327 tabwidth = self._asktabwidth()
1328 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001329 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001330 self.set_region(head, tail, chars, lines)
1331
1332 def toggle_tabs_event(self, event):
1333 if self.askyesno(
1334 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001335 "Turn tabs " + ("on", "off")[self.usetabs] +
1336 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001337 ("will be", "remains at")[self.usetabs] + " 8." +
1338 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001339 parent=self.text):
1340 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001341 # Try to prevent inconsistent indentation.
1342 # User must change indent width manually after using tabs.
1343 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001344 return "break"
1345
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001346 # XXX this isn't bound to anything -- see tabwidth comments
1347## def change_tabwidth_event(self, event):
1348## new = self._asktabwidth()
1349## if new != self.tabwidth:
1350## self.tabwidth = new
1351## self.set_indentation_params(0, guess=0)
1352## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001353
1354 def change_indentwidth_event(self, event):
1355 new = self.askinteger(
1356 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001357 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001358 parent=self.text,
1359 initialvalue=self.indentwidth,
1360 minvalue=2,
1361 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001362 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001363 self.indentwidth = new
1364 return "break"
1365
1366 def get_region(self):
1367 text = self.text
1368 first, last = self.get_selection_indices()
1369 if first and last:
1370 head = text.index(first + " linestart")
1371 tail = text.index(last + "-1c lineend +1c")
1372 else:
1373 head = text.index("insert linestart")
1374 tail = text.index("insert lineend +1c")
1375 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001376 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001377 return head, tail, chars, lines
1378
1379 def set_region(self, head, tail, chars, lines):
1380 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001381 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001382 if newchars == chars:
1383 text.bell()
1384 return
1385 text.tag_remove("sel", "1.0", "end")
1386 text.mark_set("insert", head)
1387 text.undo_block_start()
1388 text.delete(head, tail)
1389 text.insert(head, newchars)
1390 text.undo_block_stop()
1391 text.tag_add("sel", head, "insert")
1392
1393 # Make string that displays as n leading blanks.
1394
1395 def _make_blanks(self, n):
1396 if self.usetabs:
1397 ntabs, nspaces = divmod(n, self.tabwidth)
1398 return '\t' * ntabs + ' ' * nspaces
1399 else:
1400 return ' ' * n
1401
1402 # Delete from beginning of line to insert point, then reinsert
1403 # column logical (meaning use tabs if appropriate) spaces.
1404
1405 def reindent_to(self, column):
1406 text = self.text
1407 text.undo_block_start()
1408 if text.compare("insert linestart", "!=", "insert"):
1409 text.delete("insert linestart", "insert")
1410 if column:
1411 text.insert("insert", self._make_blanks(column))
1412 text.undo_block_stop()
1413
1414 def _asktabwidth(self):
1415 return self.askinteger(
1416 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001417 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001418 parent=self.text,
1419 initialvalue=self.indentwidth,
1420 minvalue=2,
1421 maxvalue=16) or self.tabwidth
1422
1423 # Guess indentwidth from text content.
1424 # Return guessed indentwidth. This should not be believed unless
1425 # it's in a reasonable range (e.g., it will be 0 if no indented
1426 # blocks are found).
1427
1428 def guess_indent(self):
1429 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1430 if opener and indented:
1431 raw, indentsmall = classifyws(opener, self.tabwidth)
1432 raw, indentlarge = classifyws(indented, self.tabwidth)
1433 else:
1434 indentsmall = indentlarge = 0
1435 return indentlarge - indentsmall
1436
1437# "line.col" -> line, as an int
1438def index2line(index):
1439 return int(float(index))
1440
1441# Look at the leading whitespace in s.
1442# Return pair (# of leading ws characters,
1443# effective # of leading blanks after expanding
1444# tabs to width tabwidth)
1445
1446def classifyws(s, tabwidth):
1447 raw = effective = 0
1448 for ch in s:
1449 if ch == ' ':
1450 raw = raw + 1
1451 effective = effective + 1
1452 elif ch == '\t':
1453 raw = raw + 1
1454 effective = (effective // tabwidth + 1) * tabwidth
1455 else:
1456 break
1457 return raw, effective
1458
1459import tokenize
1460_tokenize = tokenize
1461del tokenize
1462
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001463class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001464
1465 # .run() chews over the Text widget, looking for a block opener
1466 # and the stmt following it. Returns a pair,
1467 # (line containing block opener, line containing stmt)
1468 # Either or both may be None.
1469
1470 def __init__(self, text, tabwidth):
1471 self.text = text
1472 self.tabwidth = tabwidth
1473 self.i = self.finished = 0
1474 self.blkopenline = self.indentedline = None
1475
1476 def readline(self):
1477 if self.finished:
1478 return ""
1479 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001480 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001481 if self.text.compare(mark, ">=", "end"):
1482 return ""
1483 return self.text.get(mark, mark + " lineend+1c")
1484
1485 def tokeneater(self, type, token, start, end, line,
1486 INDENT=_tokenize.INDENT,
1487 NAME=_tokenize.NAME,
1488 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1489 if self.finished:
1490 pass
1491 elif type == NAME and token in OPENERS:
1492 self.blkopenline = line
1493 elif type == INDENT and self.blkopenline:
1494 self.indentedline = line
1495 self.finished = 1
1496
1497 def run(self):
1498 save_tabsize = _tokenize.tabsize
1499 _tokenize.tabsize = self.tabwidth
1500 try:
1501 try:
Trent Nelson428de652008-03-18 22:41:35 +00001502 tokens = _tokenize.generate_tokens(self.readline)
1503 for token in tokens:
1504 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001505 except _tokenize.TokenError:
1506 # since we cut off the tokenizer early, we can trigger
1507 # spurious errors
1508 pass
1509 finally:
1510 _tokenize.tabsize = save_tabsize
1511 return self.blkopenline, self.indentedline
1512
1513### end autoindent code ###
1514
David Scherer7aced172000-08-15 01:13:23 +00001515def prepstr(s):
1516 # Helper to extract the underscore from a string, e.g.
1517 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001518 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001519 if i >= 0:
1520 s = s[:i] + s[i+1:]
1521 return i, s
1522
1523
1524keynames = {
1525 'bracketleft': '[',
1526 'bracketright': ']',
1527 'slash': '/',
1528}
1529
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001530def get_accelerator(keydefs, eventname):
1531 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001532 if not keylist:
1533 return ""
1534 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001535 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001536 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1537 s = re.sub("Key-", "", s)
1538 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1539 s = re.sub("Control-", "Ctrl-", s)
1540 s = re.sub("-", "+", s)
1541 s = re.sub("><", " ", s)
1542 s = re.sub("<", "", s)
1543 s = re.sub(">", "", s)
1544 return s
1545
1546
1547def fixwordbreaks(root):
1548 # Make sure that Tk's double-click and next/previous word
1549 # operations use our definition of a word (i.e. an identifier)
1550 tk = root.tk
1551 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1552 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1553 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1554
1555
1556def test():
1557 root = Tk()
1558 fixwordbreaks(root)
1559 root.withdraw()
1560 if sys.argv[1:]:
1561 filename = sys.argv[1]
1562 else:
1563 filename = None
1564 edit = EditorWindow(root=root, filename=filename)
1565 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001566 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001567 root.mainloop()
1568 root.destroy()
1569
1570if __name__ == '__main__':
1571 test()