blob: a7970ba036f6ed9b8bf28b1b0cc1b9c276b5c931 [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
David Scherer7aced172000-08-15 01:13:23 +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:
Kurt B. Kaiserd953f6e2007-08-31 21:40:34 +000085 EditorWindow.help_url = "http://docs.python.org/dev/3.0/"
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. Kaiserb1754452005-11-18 22:05:48 +0000110 self.text = text = MultiCallCreator(Text)(
111 text_frame, name='text', padx=5, wrap='none',
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000112 width=self.width,
Steven M. Gavadc72f482002-01-03 11:51:07 +0000113 height=idleConf.GetOption('main','EditorWindow','height') )
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000114 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000115
116 self.createmenubar()
117 self.apply_bindings()
118
119 self.top.protocol("WM_DELETE_WINDOW", self.close)
120 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000121 if macosxSupport.runningAsOSXApp():
122 # Command-W on editorwindows doesn't work without this.
123 text.bind('<<close-window>>', self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000124 text.bind("<<cut>>", self.cut)
125 text.bind("<<copy>>", self.copy)
126 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000127 text.bind("<<center-insert>>", self.center_insert_event)
128 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000129 text.bind("<<python-docs>>", self.python_docs)
130 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000131 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000132 text.bind("<<open-module>>", self.open_module)
133 text.bind("<<do-nothing>>", lambda event: "break")
134 text.bind("<<select-all>>", self.select_all)
135 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000136 text.bind("<<find>>", self.find_event)
137 text.bind("<<find-again>>", self.find_again_event)
138 text.bind("<<find-in-files>>", self.find_in_files_event)
139 text.bind("<<find-selection>>", self.find_selection_event)
140 text.bind("<<replace>>", self.replace_event)
141 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000142 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000143 text.bind("<<smart-backspace>>",self.smart_backspace_event)
144 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
145 text.bind("<<smart-indent>>",self.smart_indent_event)
146 text.bind("<<indent-region>>",self.indent_region_event)
147 text.bind("<<dedent-region>>",self.dedent_region_event)
148 text.bind("<<comment-region>>",self.comment_region_event)
149 text.bind("<<uncomment-region>>",self.uncomment_region_event)
150 text.bind("<<tabify-region>>",self.tabify_region_event)
151 text.bind("<<untabify-region>>",self.untabify_region_event)
152 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
153 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000154 text.bind("<Left>", self.move_at_edge_if_selection(0))
155 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000156 text.bind("<<del-word-left>>", self.del_word_left)
157 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000158 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000159
David Scherer7aced172000-08-15 01:13:23 +0000160 if flist:
161 flist.inversedict[self] = key
162 if key:
163 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000164 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000165 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
166 text.bind("<<open-class-browser>>", self.open_class_browser)
167 text.bind("<<open-path-browser>>", self.open_path_browser)
168
Steven M. Gava898a3652001-10-07 11:10:44 +0000169 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000170 vbar['command'] = text.yview
171 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000172 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000173 fontWeight = 'normal'
174 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000175 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000176 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
177 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
178 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000179 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
180 text.pack(side=TOP, fill=BOTH, expand=1)
181 text.focus_set()
182
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000183 # usetabs true -> literal tab characters are used by indent and
184 # dedent cmds, possibly mixed with spaces if
185 # indentwidth is not a multiple of tabwidth,
186 # which will cause Tabnanny to nag!
187 # false -> tab characters are converted to spaces by indent
188 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000189 # Although use-spaces=0 can be configured manually in config-main.def,
190 # configuration of tabs v. spaces is not supported in the configuration
191 # dialog. IDLE promotes the preferred Python indentation: use spaces!
192 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
193 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000194
195 # tabwidth is the display width of a literal tab character.
196 # CAUTION: telling Tk to use anything other than its default
197 # tab setting causes it to use an entirely different tabbing algorithm,
198 # treating tab stops as fixed distances from the left margin.
199 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000200 self.tabwidth = 8 # must remain 8 until Tk is fixed.
201
202 # indentwidth is the number of screen characters per indent level.
203 # The recommended Python indentation is four spaces.
204 self.indentwidth = self.tabwidth
205 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000206
207 # If context_use_ps1 is true, parsing searches back for a ps1 line;
208 # else searches for a popular (if, def, ...) Python stmt.
209 self.context_use_ps1 = False
210
211 # When searching backwards for a reliable place to begin parsing,
212 # first start num_context_lines[0] lines back, then
213 # num_context_lines[1] lines back if that didn't work, and so on.
214 # The last value should be huge (larger than the # of lines in a
215 # conceivable file).
216 # Making the initial values larger slows things down more often.
217 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000218 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000219 self.undo = undo = self.UndoDelegator()
220 per.insertfilter(undo)
221 text.undo_block_start = undo.undo_block_start
222 text.undo_block_stop = undo.undo_block_stop
223 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000224 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000225 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000226 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000227 self.good_load = False
228 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000229 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000230 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000231 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000232 if io.loadfile(filename):
233 self.good_load = True
234 is_py_src = self.ispythonsource(filename)
235 self.set_indentation_params(is_py_src)
236 if is_py_src:
237 self.color = color = self.ColorDelegator()
238 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000239 else:
240 io.set_filename(filename)
Christian Heimesa156e092008-02-16 07:38:31 +0000241 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000242 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000243 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000244 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000245 menu = self.menudict.get('windows')
246 if menu:
247 end = menu.index("end")
248 if end is None:
249 end = -1
250 if end >= 0:
251 menu.add_separator()
252 end = end + 1
253 self.wmenu_end = end
254 WindowList.register_callback(self.postwindowsmenu)
255
256 # Some abstractions so IDLE extensions are cross-IDE
257 self.askyesno = tkMessageBox.askyesno
258 self.askinteger = tkSimpleDialog.askinteger
259 self.showerror = tkMessageBox.showerror
260
Martin v. Löwis307021f2005-11-27 16:59:04 +0000261 def _filename_to_unicode(self, filename):
262 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000263 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000264 return filename
265 else:
266 try:
267 return filename.decode(self.filesystemencoding)
268 except UnicodeDecodeError:
269 # XXX
270 try:
271 return filename.decode(self.encoding)
272 except UnicodeDecodeError:
273 # byte-to-byte conversion
274 return filename.decode('iso8859-1')
275
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000276 def new_callback(self, event):
277 dirname, basename = self.io.defaultfilename()
278 self.flist.new(dirname)
279 return "break"
280
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000281 def home_callback(self, event):
282 if (event.state & 12) != 0 and event.keysym == "Home":
283 # state&1==shift, state&4==control, state&8==alt
284 return # <Modifier-Home>; fall back to class binding
285
286 if self.text.index("iomark") and \
287 self.text.compare("iomark", "<=", "insert lineend") and \
288 self.text.compare("insert linestart", "<=", "iomark"):
289 insertpt = int(self.text.index("iomark").split(".")[1])
290 else:
291 line = self.text.get("insert linestart", "insert lineend")
292 for insertpt in xrange(len(line)):
293 if line[insertpt] not in (' ','\t'):
294 break
295 else:
296 insertpt=len(line)
297
298 lineat = int(self.text.index("insert").split('.')[1])
299
300 if insertpt == lineat:
301 insertpt = 0
302
303 dest = "insert linestart+"+str(insertpt)+"c"
304
305 if (event.state&1) == 0:
306 # shift not pressed
307 self.text.tag_remove("sel", "1.0", "end")
308 else:
309 if not self.text.index("sel.first"):
310 self.text.mark_set("anchor","insert")
311
312 first = self.text.index(dest)
313 last = self.text.index("anchor")
314
315 if self.text.compare(first,">",last):
316 first,last = last,first
317
318 self.text.tag_remove("sel", "1.0", "end")
319 self.text.tag_add("sel", first, last)
320
321 self.text.mark_set("insert", dest)
322 self.text.see("insert")
323 return "break"
324
David Scherer7aced172000-08-15 01:13:23 +0000325 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000326 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000327 if macosxSupport.runningAsOSXApp():
328 # Insert some padding to avoid obscuring some of the statusbar
329 # by the resize widget.
330 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000331 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
332 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
333 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000334 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
335 self.text.event_add("<<set-line-and-column>>",
336 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000337 self.text.after_idle(self.set_line_and_column)
338
339 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000340 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000341 self.status_bar.set_label('column', 'Col: %s' % column)
342 self.status_bar.set_label('line', 'Ln: %s' % line)
343
David Scherer7aced172000-08-15 01:13:23 +0000344 menu_specs = [
345 ("file", "_File"),
346 ("edit", "_Edit"),
347 ("format", "F_ormat"),
348 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000349 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000350 ("windows", "_Windows"),
351 ("help", "_Help"),
352 ]
353
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000354 if macosxSupport.runningAsOSXApp():
355 del menu_specs[-3]
356 menu_specs[-2] = ("windows", "_Window")
357
358
David Scherer7aced172000-08-15 01:13:23 +0000359 def createmenubar(self):
360 mbar = self.menubar
361 self.menudict = menudict = {}
362 for name, label in self.menu_specs:
363 underline, label = prepstr(label)
364 menudict[name] = menu = Menu(mbar, name=name)
365 mbar.add_cascade(label=label, menu=menu, underline=underline)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000366 if sys.platform == 'darwin' and '.framework' in sys.executable:
367 # Insert the application menu
368 menudict['application'] = menu = Menu(mbar, name='apple')
369 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000370 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000371 self.recent_files_menu = Menu(self.menubar)
372 self.menudict['file'].insert_cascade(3, label='Recent Files',
373 underline=0,
374 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000375 self.base_helpmenu_length = self.menudict['help'].index(END)
376 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000377
378 def postwindowsmenu(self):
379 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000380 menu = self.menudict['windows']
381 end = menu.index("end")
382 if end is None:
383 end = -1
384 if end > self.wmenu_end:
385 menu.delete(self.wmenu_end+1, end)
386 WindowList.add_windows_to_menu(menu)
387
388 rmenu = None
389
390 def right_menu_event(self, event):
391 self.text.tag_remove("sel", "1.0", "end")
392 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
393 if not self.rmenu:
394 self.make_rmenu()
395 rmenu = self.rmenu
396 self.event = event
397 iswin = sys.platform[:3] == 'win'
398 if iswin:
399 self.text.config(cursor="arrow")
400 rmenu.tk_popup(event.x_root, event.y_root)
401 if iswin:
402 self.text.config(cursor="ibeam")
403
404 rmenu_specs = [
405 # ("Label", "<<virtual-event>>"), ...
406 ("Close", "<<close-window>>"), # Example
407 ]
408
409 def make_rmenu(self):
410 rmenu = Menu(self.text, tearoff=0)
411 for label, eventname in self.rmenu_specs:
412 def command(text=self.text, eventname=eventname):
413 text.event_generate(eventname)
414 rmenu.add_command(label=label, command=command)
415 self.rmenu = rmenu
416
417 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000418 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000419
Steven M. Gava3b55a892001-11-21 05:56:26 +0000420 def config_dialog(self, event=None):
421 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000422
David Scherer7aced172000-08-15 01:13:23 +0000423 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000424 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000425 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000426
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000427 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000428 if sys.platform[:3] == 'win':
Steven M. Gava931625d2002-04-22 00:38:26 +0000429 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000430 else:
431 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000432 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000433
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000434 def cut(self,event):
435 self.text.event_generate("<<Cut>>")
436 return "break"
437
438 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000439 if not self.text.tag_ranges("sel"):
440 # There is no selection, so do nothing and maybe interrupt.
441 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000442 self.text.event_generate("<<Copy>>")
443 return "break"
444
445 def paste(self,event):
446 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000447 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000448 return "break"
449
David Scherer7aced172000-08-15 01:13:23 +0000450 def select_all(self, event=None):
451 self.text.tag_add("sel", "1.0", "end-1c")
452 self.text.mark_set("insert", "1.0")
453 self.text.see("insert")
454 return "break"
455
456 def remove_selection(self, event=None):
457 self.text.tag_remove("sel", "1.0", "end")
458 self.text.see("insert")
459
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000460 def move_at_edge_if_selection(self, edge_index):
461 """Cursor move begins at start or end of selection
462
463 When a left/right cursor key is pressed create and return to Tkinter a
464 function which causes a cursor move from the associated edge of the
465 selection.
466
467 """
468 self_text_index = self.text.index
469 self_text_mark_set = self.text.mark_set
470 edges_table = ("sel.first+1c", "sel.last-1c")
471 def move_at_edge(event):
472 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
473 try:
474 self_text_index("sel.first")
475 self_text_mark_set("insert", edges_table[edge_index])
476 except TclError:
477 pass
478 return move_at_edge
479
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000480 def del_word_left(self, event):
481 self.text.event_generate('<Meta-Delete>')
482 return "break"
483
484 def del_word_right(self, event):
485 self.text.event_generate('<Meta-d>')
486 return "break"
487
Steven M. Gavac5976402002-01-04 03:06:08 +0000488 def find_event(self, event):
489 SearchDialog.find(self.text)
490 return "break"
491
492 def find_again_event(self, event):
493 SearchDialog.find_again(self.text)
494 return "break"
495
496 def find_selection_event(self, event):
497 SearchDialog.find_selection(self.text)
498 return "break"
499
500 def find_in_files_event(self, event):
501 GrepDialog.grep(self.text, self.io, self.flist)
502 return "break"
503
504 def replace_event(self, event):
505 ReplaceDialog.replace(self.text)
506 return "break"
507
508 def goto_line_event(self, event):
509 text = self.text
510 lineno = tkSimpleDialog.askinteger("Goto",
511 "Go to line number:",parent=text)
512 if lineno is None:
513 return "break"
514 if lineno <= 0:
515 text.bell()
516 return "break"
517 text.mark_set("insert", "%d.0" % lineno)
518 text.see("insert")
519
David Scherer7aced172000-08-15 01:13:23 +0000520 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000521 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000522 try:
523 name = self.text.get("sel.first", "sel.last")
524 except TclError:
525 name = ""
526 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000527 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000528 name = tkSimpleDialog.askstring("Module",
529 "Enter the name of a Python module\n"
530 "to search on sys.path and open:",
531 parent=self.text, initialvalue=name)
532 if name:
533 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000534 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000535 return
David Scherer7aced172000-08-15 01:13:23 +0000536 # XXX Ought to insert current file's directory in front of path
537 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000538 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000539 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000540 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
541 return
542 if type != imp.PY_SOURCE:
543 tkMessageBox.showerror("Unsupported type",
544 "%s is not a source module" % name, parent=self.text)
545 return
546 if f:
547 f.close()
548 if self.flist:
549 self.flist.open(file)
550 else:
551 self.io.loadfile(file)
552
553 def open_class_browser(self, event=None):
554 filename = self.io.filename
555 if not filename:
556 tkMessageBox.showerror(
557 "No filename",
558 "This buffer has no associated filename",
559 master=self.text)
560 self.text.focus_set()
561 return None
562 head, tail = os.path.split(filename)
563 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000564 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000565 ClassBrowser.ClassBrowser(self.flist, base, [head])
566
567 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000568 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000569 PathBrowser.PathBrowser(self.flist)
570
571 def gotoline(self, lineno):
572 if lineno is not None and lineno > 0:
573 self.text.mark_set("insert", "%d.0" % lineno)
574 self.text.tag_remove("sel", "1.0", "end")
575 self.text.tag_add("sel", "insert", "insert +1l")
576 self.center()
577
578 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000579 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000580 return True
David Scherer7aced172000-08-15 01:13:23 +0000581 base, ext = os.path.splitext(os.path.basename(filename))
582 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000583 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000584 line = self.text.get('1.0', '1.0 lineend')
585 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000586
587 def close_hook(self):
588 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000589 self.flist.unregister_maybe_terminate(self)
590 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000591
592 def set_close_hook(self, close_hook):
593 self.close_hook = close_hook
594
595 def filename_change_hook(self):
596 if self.flist:
597 self.flist.filename_changed_edit(self)
598 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000599 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000600 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000601
Christian Heimesa156e092008-02-16 07:38:31 +0000602 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000603 if self.color:
604 return
Christian Heimesa156e092008-02-16 07:38:31 +0000605 if self.ispythonsource(self.io.filename):
606 self.color = self.ColorDelegator()
607 # can add more colorizers here...
608 if self.color:
609 self.per.removefilter(self.undo)
610 self.per.insertfilter(self.color)
611 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000612
Christian Heimesa156e092008-02-16 07:38:31 +0000613 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000614 if not self.color:
615 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000616 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000617 self.per.removefilter(self.color)
618 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000619
Steven M. Gavab77d3432002-03-02 07:16:21 +0000620 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000621 "Update the colour theme"
622 # Called from self.filename_change_hook and from configDialog.py
623 self._rmcolorizer()
624 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000625 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000626 normal_colors = idleConf.GetHighlight(theme, 'normal')
627 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
628 select_colors = idleConf.GetHighlight(theme, 'hilite')
629 self.text.config(
630 foreground=normal_colors['foreground'],
631 background=normal_colors['background'],
632 insertbackground=cursor_color,
633 selectforeground=select_colors['foreground'],
634 selectbackground=select_colors['background'],
635 )
David Scherer7aced172000-08-15 01:13:23 +0000636
Guido van Rossum33d26892007-08-05 15:29:28 +0000637 IDENTCHARS = string.ascii_letters + string.digits + "_"
638
639 def colorize_syntax_error(self, text, pos):
640 text.tag_add("ERROR", pos)
641 char = text.get(pos)
642 if char and char in self.IDENTCHARS:
643 text.tag_add("ERROR", pos + " wordstart", pos)
644 if '\n' == text.get(pos): # error at line end
645 text.mark_set("insert", pos)
646 else:
647 text.mark_set("insert", pos + "+1c")
648 text.see(pos)
649
Steven M. Gavab1585412002-03-12 00:21:56 +0000650 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000651 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000652 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000653 fontWeight='normal'
654 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
655 fontWeight='bold'
656 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
657 idleConf.GetOption('main','EditorWindow','font-size'),
658 fontWeight))
659
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000660 def RemoveKeybindings(self):
661 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000662 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000663 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000664 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000665 self.text.event_delete(event, *keylist)
666 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000667 xkeydefs = idleConf.GetExtensionBindings(extensionName)
668 if xkeydefs:
669 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000670 self.text.event_delete(event, *keylist)
671
672 def ApplyKeybindings(self):
673 "Update the keybindings after they are changed"
674 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000675 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000676 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000677 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000678 xkeydefs = idleConf.GetExtensionBindings(extensionName)
679 if xkeydefs:
680 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000681 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000682 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000683 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000684 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000685 for item in menu[1]:
686 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000687 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000688 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000689 menu = self.menudict[menubarItem]
690 end = menu.index(END) + 1
691 for index in range(0, end):
692 if menu.type(index) == 'command':
693 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000694 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000695 itemName = menu.entrycget(index, 'label')
696 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000697 if menubarItem in menuEventDict:
698 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000699 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000700 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000701 accel = get_accelerator(keydefs, event)
702 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000703
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000704 def set_notabs_indentwidth(self):
705 "Update the indentwidth if changed and not using tabs in this window"
706 # Called from configDialog.py
707 if not self.usetabs:
708 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
709 type='int')
710
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000711 def reset_help_menu_entries(self):
712 "Update the additional help entries on the Help menu"
713 help_list = idleConf.GetAllExtraHelpSourcesList()
714 helpmenu = self.menudict['help']
715 # first delete the extra help entries, if any
716 helpmenu_length = helpmenu.index(END)
717 if helpmenu_length > self.base_helpmenu_length:
718 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
719 # then rebuild them
720 if help_list:
721 helpmenu.add_separator()
722 for entry in help_list:
723 cmd = self.__extra_help_callback(entry[1])
724 helpmenu.add_command(label=entry[0], command=cmd)
725 # and update the menu dictionary
726 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000727
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000728 def __extra_help_callback(self, helpfile):
729 "Create a callback with the helpfile value frozen at definition time"
730 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000731 if not helpfile.startswith(('www', 'http')):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000732 url = os.path.normpath(helpfile)
733 if sys.platform[:3] == 'win':
734 os.startfile(helpfile)
735 else:
736 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000737 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000738
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000739 def update_recent_files_list(self, new_file=None):
740 "Load and update the recent files list and menus"
741 rf_list = []
742 if os.path.exists(self.recent_files_path):
743 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000744 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000745 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000746 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000747 rf_list_file.close()
748 if new_file:
749 new_file = os.path.abspath(new_file) + '\n'
750 if new_file in rf_list:
751 rf_list.remove(new_file) # move to top
752 rf_list.insert(0, new_file)
753 # clean and save the recent files list
754 bad_paths = []
755 for path in rf_list:
756 if '\0' in path or not os.path.exists(path[0:-1]):
757 bad_paths.append(path)
758 rf_list = [path for path in rf_list if path not in bad_paths]
759 ulchars = "1234567890ABCDEFGHIJK"
760 rf_list = rf_list[0:len(ulchars)]
761 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000762 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000763 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000764 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000765 rf_file.close()
766 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000767 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000768 menu = instance.recent_files_menu
769 menu.delete(1, END) # clear, and rebuild:
770 for i, file in zip(count(), rf_list):
771 file_name = file[0:-1] # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000772 # make unicode string to display non-ASCII chars correctly
773 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000774 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000775 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000776 command=callback,
777 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000778
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000779 def __recent_file_callback(self, file_name):
780 def open_recent_file(fn_closure=file_name):
781 self.io.open(editFile=fn_closure)
782 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000783
David Scherer7aced172000-08-15 01:13:23 +0000784 def saved_change_hook(self):
785 short = self.short_title()
786 long = self.long_title()
787 if short and long:
788 title = short + " - " + long
789 elif short:
790 title = short
791 elif long:
792 title = long
793 else:
794 title = "Untitled"
795 icon = short or long or title
796 if not self.get_saved():
797 title = "*%s*" % title
798 icon = "*%s" % icon
799 self.top.wm_title(title)
800 self.top.wm_iconname(icon)
801
802 def get_saved(self):
803 return self.undo.get_saved()
804
805 def set_saved(self, flag):
806 self.undo.set_saved(flag)
807
808 def reset_undo(self):
809 self.undo.reset_undo()
810
811 def short_title(self):
812 filename = self.io.filename
813 if filename:
814 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000815 # return unicode string to display non-ASCII chars correctly
816 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000817
818 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000819 # return unicode string to display non-ASCII chars correctly
820 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000821
822 def center_insert_event(self, event):
823 self.center()
824
825 def center(self, mark="insert"):
826 text = self.text
827 top, bot = self.getwindowlines()
828 lineno = self.getlineno(mark)
829 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000830 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000831 text.yview(float(newtop))
832
833 def getwindowlines(self):
834 text = self.text
835 top = self.getlineno("@0,0")
836 bot = self.getlineno("@0,65535")
837 if top == bot and text.winfo_height() == 1:
838 # Geometry manager hasn't run yet
839 height = int(text['height'])
840 bot = top + height - 1
841 return top, bot
842
843 def getlineno(self, mark="insert"):
844 text = self.text
845 return int(float(text.index(mark)))
846
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000847 def get_geometry(self):
848 "Return (width, height, x, y)"
849 geom = self.top.wm_geometry()
850 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000851 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000852
David Scherer7aced172000-08-15 01:13:23 +0000853 def close_event(self, event):
854 self.close()
855
856 def maybesave(self):
857 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000858 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000859 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000860 self.top.deiconify()
861 self.top.lower()
862 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000863 return self.io.maybesave()
864
865 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000866 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000867 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000868 self._close()
869 return reply
870
871 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000872 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000873 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000874 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000875 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000876 self.io.close()
877 self.io = None
878 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000879 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000880 self.color.close(False)
881 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000882 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000883 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000884 self.per.close()
885 self.per = None
886 self.top.destroy()
887 if self.close_hook:
888 # unless override: unregister from flist, terminate if last window
889 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000890
891 def load_extensions(self):
892 self.extensions = {}
893 self.load_standard_extensions()
894
895 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000896 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000897 if hasattr(ins, "close"):
898 ins.close()
899 self.extensions = {}
900
901 def load_standard_extensions(self):
902 for name in self.get_standard_extension_names():
903 try:
904 self.load_extension(name)
905 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000906 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000907 traceback.print_exc()
908
909 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000910 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000911
912 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000913 try:
914 mod = __import__(name, globals(), locals(), [])
915 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000916 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000917 raise
David Scherer7aced172000-08-15 01:13:23 +0000918 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000919 keydefs = idleConf.GetExtensionBindings(name)
920 if hasattr(cls, "menudefs"):
921 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000922 ins = cls(self)
923 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000924 if keydefs:
925 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000926 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000927 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000928 while methodname[:1] == '<':
929 methodname = methodname[1:]
930 while methodname[-1:] == '>':
931 methodname = methodname[:-1]
932 methodname = methodname + "_event"
933 if hasattr(ins, methodname):
934 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000935
936 def apply_bindings(self, keydefs=None):
937 if keydefs is None:
938 keydefs = self.Bindings.default_keydefs
939 text = self.text
940 text.keydefs = keydefs
941 for event, keylist in keydefs.items():
942 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000943 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000944
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000945 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000946 """Add appropriate entries to the menus and submenus
947
948 Menus that are absent or None in self.menudict are ignored.
949 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000950 if menudefs is None:
951 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000952 if keydefs is None:
953 keydefs = self.Bindings.default_keydefs
954 menudict = self.menudict
955 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000956 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000957 menu = menudict.get(mname)
958 if not menu:
959 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000960 for entry in entrylist:
961 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000962 menu.add_separator()
963 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000964 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000965 checkbutton = (label[:1] == '!')
966 if checkbutton:
967 label = label[1:]
968 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000969 accelerator = get_accelerator(keydefs, eventname)
970 def command(text=text, eventname=eventname):
971 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000972 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000973 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000974 menu.add_checkbutton(label=label, underline=underline,
975 command=command, accelerator=accelerator,
976 variable=var)
977 else:
978 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000979 command=command,
980 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000981
982 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000983 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +0000984 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000985 value = var.get()
986 return value
987 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000988 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +0000989
990 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000991 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +0000992 if var:
993 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000994 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000995 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +0000996
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000997 def get_var_obj(self, name, vartype=None):
998 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +0000999 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001000 # create a Tkinter variable object with self.text as master:
1001 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001002 return var
1003
1004 # Tk implementations of "virtual text methods" -- each platform
1005 # reusing IDLE's support code needs to define these for its GUI's
1006 # flavor of widget.
1007
1008 # Is character at text_index in a Python string? Return 0 for
1009 # "guaranteed no", true for anything else. This info is expensive
1010 # to compute ab initio, but is probably already known by the
1011 # platform's colorizer.
1012
1013 def is_char_in_string(self, text_index):
1014 if self.color:
1015 # Return true iff colorizer hasn't (re)gotten this far
1016 # yet, or the character is tagged as being in a string
1017 return self.text.tag_prevrange("TODO", text_index) or \
1018 "STRING" in self.text.tag_names(text_index)
1019 else:
1020 # The colorizer is missing: assume the worst
1021 return 1
1022
1023 # If a selection is defined in the text widget, return (start,
1024 # end) as Tkinter text indices, otherwise return (None, None)
1025 def get_selection_indices(self):
1026 try:
1027 first = self.text.index("sel.first")
1028 last = self.text.index("sel.last")
1029 return first, last
1030 except TclError:
1031 return None, None
1032
1033 # Return the text widget's current view of what a tab stop means
1034 # (equivalent width in spaces).
1035
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001036 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001037 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1038 return int(current)
1039
1040 # Set the text widget's current view of what a tab stop means.
1041
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001042 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001043 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001044 if self.get_tk_tabwidth() != newtabwidth:
1045 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001046 pixels = text.tk.call("font", "measure", text["font"],
1047 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001048 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001049 text.configure(tabs=pixels)
1050
Guido van Rossum33d26892007-08-05 15:29:28 +00001051### begin autoindent code ### (configuration was moved to beginning of class)
1052
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001053 def set_indentation_params(self, is_py_src, guess=True):
1054 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001055 i = self.guess_indent()
1056 if 2 <= i <= 8:
1057 self.indentwidth = i
1058 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001059 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001060 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001061
1062 def smart_backspace_event(self, event):
1063 text = self.text
1064 first, last = self.get_selection_indices()
1065 if first and last:
1066 text.delete(first, last)
1067 text.mark_set("insert", first)
1068 return "break"
1069 # Delete whitespace left, until hitting a real char or closest
1070 # preceding virtual tab stop.
1071 chars = text.get("insert linestart", "insert")
1072 if chars == '':
1073 if text.compare("insert", ">", "1.0"):
1074 # easy: delete preceding newline
1075 text.delete("insert-1c")
1076 else:
1077 text.bell() # at start of buffer
1078 return "break"
1079 if chars[-1] not in " \t":
1080 # easy: delete preceding real char
1081 text.delete("insert-1c")
1082 return "break"
1083 # Ick. It may require *inserting* spaces if we back up over a
1084 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001085 tabwidth = self.tabwidth
1086 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001087 assert have > 0
1088 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001089 # Debug prompt is multilined....
1090 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001091 ncharsdeleted = 0
1092 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001093 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001094 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001095 chars = chars[:-1]
1096 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001097 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001098 if have <= want or chars[-1] not in " \t":
1099 break
1100 text.undo_block_start()
1101 text.delete("insert-%dc" % ncharsdeleted, "insert")
1102 if have < want:
1103 text.insert("insert", ' ' * (want - have))
1104 text.undo_block_stop()
1105 return "break"
1106
1107 def smart_indent_event(self, event):
1108 # if intraline selection:
1109 # delete it
1110 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001111 # do indent-region
1112 # else:
1113 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001114 text = self.text
1115 first, last = self.get_selection_indices()
1116 text.undo_block_start()
1117 try:
1118 if first and last:
1119 if index2line(first) != index2line(last):
1120 return self.indent_region_event(event)
1121 text.delete(first, last)
1122 text.mark_set("insert", first)
1123 prefix = text.get("insert linestart", "insert")
1124 raw, effective = classifyws(prefix, self.tabwidth)
1125 if raw == len(prefix):
1126 # only whitespace to the left
1127 self.reindent_to(effective + self.indentwidth)
1128 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001129 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001130 if self.usetabs:
1131 pad = '\t'
1132 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001133 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001134 n = self.indentwidth
1135 pad = ' ' * (n - effective % n)
1136 text.insert("insert", pad)
1137 text.see("insert")
1138 return "break"
1139 finally:
1140 text.undo_block_stop()
1141
1142 def newline_and_indent_event(self, event):
1143 text = self.text
1144 first, last = self.get_selection_indices()
1145 text.undo_block_start()
1146 try:
1147 if first and last:
1148 text.delete(first, last)
1149 text.mark_set("insert", first)
1150 line = text.get("insert linestart", "insert")
1151 i, n = 0, len(line)
1152 while i < n and line[i] in " \t":
1153 i = i+1
1154 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001155 # the cursor is in or at leading indentation in a continuation
1156 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001157 text.insert("insert linestart", '\n')
1158 return "break"
1159 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001160 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001161 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001162 last_line_of_prompt = sys.ps1.split('\n')[-1]
1163 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001164 line = line[:-1]
1165 i = i+1
1166 if i:
1167 text.delete("insert - %d chars" % i, "insert")
1168 # strip whitespace after insert point
1169 while text.get("insert") in " \t":
1170 text.delete("insert")
1171 # start new line
1172 text.insert("insert", '\n')
1173
1174 # adjust indentation for continuations and block
1175 # open/close first need to find the last stmt
1176 lno = index2line(text.index('insert'))
1177 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001178 if not self.context_use_ps1:
1179 for context in self.num_context_lines:
1180 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001181 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001182 rawtext = text.get(startatindex, "insert")
1183 y.set_str(rawtext)
1184 bod = y.find_good_parse_start(
1185 self.context_use_ps1,
1186 self._build_char_in_string_func(startatindex))
1187 if bod is not None or startat == 1:
1188 break
1189 y.set_lo(bod or 0)
1190 else:
1191 r = text.tag_prevrange("console", "insert")
1192 if r:
1193 startatindex = r[1]
1194 else:
1195 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001196 rawtext = text.get(startatindex, "insert")
1197 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001198 y.set_lo(0)
1199
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001200 c = y.get_continuation_type()
1201 if c != PyParse.C_NONE:
1202 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001203 if c == PyParse.C_STRING_FIRST_LINE:
1204 # after the first line of a string; do not indent at all
1205 pass
1206 elif c == PyParse.C_STRING_NEXT_LINES:
1207 # inside a string which started before this line;
1208 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001209 text.insert("insert", indent)
1210 elif c == PyParse.C_BRACKET:
1211 # line up with the first (if any) element of the
1212 # last open bracket structure; else indent one
1213 # level beyond the indent of the line with the
1214 # last open bracket
1215 self.reindent_to(y.compute_bracket_indent())
1216 elif c == PyParse.C_BACKSLASH:
1217 # if more than one line in this stmt already, just
1218 # mimic the current indent; else if initial line
1219 # has a start on an assignment stmt, indent to
1220 # beyond leftmost =; else to beyond first chunk of
1221 # non-whitespace on initial line
1222 if y.get_num_lines_in_stmt() > 1:
1223 text.insert("insert", indent)
1224 else:
1225 self.reindent_to(y.compute_backslash_indent())
1226 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001227 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001228 return "break"
1229
1230 # This line starts a brand new stmt; indent relative to
1231 # indentation of initial line of closest preceding
1232 # interesting stmt.
1233 indent = y.get_base_indent_string()
1234 text.insert("insert", indent)
1235 if y.is_block_opener():
1236 self.smart_indent_event(event)
1237 elif indent and y.is_block_closer():
1238 self.smart_backspace_event(event)
1239 return "break"
1240 finally:
1241 text.see("insert")
1242 text.undo_block_stop()
1243
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001244 # Our editwin provides a is_char_in_string function that works
1245 # with a Tk text index, but PyParse only knows about offsets into
1246 # a string. This builds a function for PyParse that accepts an
1247 # offset.
1248
1249 def _build_char_in_string_func(self, startindex):
1250 def inner(offset, _startindex=startindex,
1251 _icis=self.is_char_in_string):
1252 return _icis(_startindex + "+%dc" % offset)
1253 return inner
1254
1255 def indent_region_event(self, event):
1256 head, tail, chars, lines = self.get_region()
1257 for pos in range(len(lines)):
1258 line = lines[pos]
1259 if line:
1260 raw, effective = classifyws(line, self.tabwidth)
1261 effective = effective + self.indentwidth
1262 lines[pos] = self._make_blanks(effective) + line[raw:]
1263 self.set_region(head, tail, chars, lines)
1264 return "break"
1265
1266 def dedent_region_event(self, event):
1267 head, tail, chars, lines = self.get_region()
1268 for pos in range(len(lines)):
1269 line = lines[pos]
1270 if line:
1271 raw, effective = classifyws(line, self.tabwidth)
1272 effective = max(effective - self.indentwidth, 0)
1273 lines[pos] = self._make_blanks(effective) + line[raw:]
1274 self.set_region(head, tail, chars, lines)
1275 return "break"
1276
1277 def comment_region_event(self, event):
1278 head, tail, chars, lines = self.get_region()
1279 for pos in range(len(lines) - 1):
1280 line = lines[pos]
1281 lines[pos] = '##' + line
1282 self.set_region(head, tail, chars, lines)
1283
1284 def uncomment_region_event(self, event):
1285 head, tail, chars, lines = self.get_region()
1286 for pos in range(len(lines)):
1287 line = lines[pos]
1288 if not line:
1289 continue
1290 if line[:2] == '##':
1291 line = line[2:]
1292 elif line[:1] == '#':
1293 line = line[1:]
1294 lines[pos] = line
1295 self.set_region(head, tail, chars, lines)
1296
1297 def tabify_region_event(self, event):
1298 head, tail, chars, lines = self.get_region()
1299 tabwidth = self._asktabwidth()
1300 for pos in range(len(lines)):
1301 line = lines[pos]
1302 if line:
1303 raw, effective = classifyws(line, tabwidth)
1304 ntabs, nspaces = divmod(effective, tabwidth)
1305 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1306 self.set_region(head, tail, chars, lines)
1307
1308 def untabify_region_event(self, event):
1309 head, tail, chars, lines = self.get_region()
1310 tabwidth = self._asktabwidth()
1311 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001312 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001313 self.set_region(head, tail, chars, lines)
1314
1315 def toggle_tabs_event(self, event):
1316 if self.askyesno(
1317 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001318 "Turn tabs " + ("on", "off")[self.usetabs] +
1319 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001320 ("will be", "remains at")[self.usetabs] + " 8." +
1321 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001322 parent=self.text):
1323 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001324 # Try to prevent inconsistent indentation.
1325 # User must change indent width manually after using tabs.
1326 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001327 return "break"
1328
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001329 # XXX this isn't bound to anything -- see tabwidth comments
1330## def change_tabwidth_event(self, event):
1331## new = self._asktabwidth()
1332## if new != self.tabwidth:
1333## self.tabwidth = new
1334## self.set_indentation_params(0, guess=0)
1335## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001336
1337 def change_indentwidth_event(self, event):
1338 new = self.askinteger(
1339 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001340 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001341 parent=self.text,
1342 initialvalue=self.indentwidth,
1343 minvalue=2,
1344 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001345 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001346 self.indentwidth = new
1347 return "break"
1348
1349 def get_region(self):
1350 text = self.text
1351 first, last = self.get_selection_indices()
1352 if first and last:
1353 head = text.index(first + " linestart")
1354 tail = text.index(last + "-1c lineend +1c")
1355 else:
1356 head = text.index("insert linestart")
1357 tail = text.index("insert lineend +1c")
1358 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001359 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001360 return head, tail, chars, lines
1361
1362 def set_region(self, head, tail, chars, lines):
1363 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001364 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001365 if newchars == chars:
1366 text.bell()
1367 return
1368 text.tag_remove("sel", "1.0", "end")
1369 text.mark_set("insert", head)
1370 text.undo_block_start()
1371 text.delete(head, tail)
1372 text.insert(head, newchars)
1373 text.undo_block_stop()
1374 text.tag_add("sel", head, "insert")
1375
1376 # Make string that displays as n leading blanks.
1377
1378 def _make_blanks(self, n):
1379 if self.usetabs:
1380 ntabs, nspaces = divmod(n, self.tabwidth)
1381 return '\t' * ntabs + ' ' * nspaces
1382 else:
1383 return ' ' * n
1384
1385 # Delete from beginning of line to insert point, then reinsert
1386 # column logical (meaning use tabs if appropriate) spaces.
1387
1388 def reindent_to(self, column):
1389 text = self.text
1390 text.undo_block_start()
1391 if text.compare("insert linestart", "!=", "insert"):
1392 text.delete("insert linestart", "insert")
1393 if column:
1394 text.insert("insert", self._make_blanks(column))
1395 text.undo_block_stop()
1396
1397 def _asktabwidth(self):
1398 return self.askinteger(
1399 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001400 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001401 parent=self.text,
1402 initialvalue=self.indentwidth,
1403 minvalue=2,
1404 maxvalue=16) or self.tabwidth
1405
1406 # Guess indentwidth from text content.
1407 # Return guessed indentwidth. This should not be believed unless
1408 # it's in a reasonable range (e.g., it will be 0 if no indented
1409 # blocks are found).
1410
1411 def guess_indent(self):
1412 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1413 if opener and indented:
1414 raw, indentsmall = classifyws(opener, self.tabwidth)
1415 raw, indentlarge = classifyws(indented, self.tabwidth)
1416 else:
1417 indentsmall = indentlarge = 0
1418 return indentlarge - indentsmall
1419
1420# "line.col" -> line, as an int
1421def index2line(index):
1422 return int(float(index))
1423
1424# Look at the leading whitespace in s.
1425# Return pair (# of leading ws characters,
1426# effective # of leading blanks after expanding
1427# tabs to width tabwidth)
1428
1429def classifyws(s, tabwidth):
1430 raw = effective = 0
1431 for ch in s:
1432 if ch == ' ':
1433 raw = raw + 1
1434 effective = effective + 1
1435 elif ch == '\t':
1436 raw = raw + 1
1437 effective = (effective // tabwidth + 1) * tabwidth
1438 else:
1439 break
1440 return raw, effective
1441
1442import tokenize
1443_tokenize = tokenize
1444del tokenize
1445
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001446class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001447
1448 # .run() chews over the Text widget, looking for a block opener
1449 # and the stmt following it. Returns a pair,
1450 # (line containing block opener, line containing stmt)
1451 # Either or both may be None.
1452
1453 def __init__(self, text, tabwidth):
1454 self.text = text
1455 self.tabwidth = tabwidth
1456 self.i = self.finished = 0
1457 self.blkopenline = self.indentedline = None
1458
1459 def readline(self):
1460 if self.finished:
1461 return ""
1462 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001463 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001464 if self.text.compare(mark, ">=", "end"):
1465 return ""
1466 return self.text.get(mark, mark + " lineend+1c")
1467
1468 def tokeneater(self, type, token, start, end, line,
1469 INDENT=_tokenize.INDENT,
1470 NAME=_tokenize.NAME,
1471 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1472 if self.finished:
1473 pass
1474 elif type == NAME and token in OPENERS:
1475 self.blkopenline = line
1476 elif type == INDENT and self.blkopenline:
1477 self.indentedline = line
1478 self.finished = 1
1479
1480 def run(self):
1481 save_tabsize = _tokenize.tabsize
1482 _tokenize.tabsize = self.tabwidth
1483 try:
1484 try:
Trent Nelson428de652008-03-18 22:41:35 +00001485 tokens = _tokenize.generate_tokens(self.readline)
1486 for token in tokens:
1487 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001488 except _tokenize.TokenError:
1489 # since we cut off the tokenizer early, we can trigger
1490 # spurious errors
1491 pass
1492 finally:
1493 _tokenize.tabsize = save_tabsize
1494 return self.blkopenline, self.indentedline
1495
1496### end autoindent code ###
1497
David Scherer7aced172000-08-15 01:13:23 +00001498def prepstr(s):
1499 # Helper to extract the underscore from a string, e.g.
1500 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001501 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001502 if i >= 0:
1503 s = s[:i] + s[i+1:]
1504 return i, s
1505
1506
1507keynames = {
1508 'bracketleft': '[',
1509 'bracketright': ']',
1510 'slash': '/',
1511}
1512
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001513def get_accelerator(keydefs, eventname):
1514 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001515 if not keylist:
1516 return ""
1517 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001518 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001519 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1520 s = re.sub("Key-", "", s)
1521 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1522 s = re.sub("Control-", "Ctrl-", s)
1523 s = re.sub("-", "+", s)
1524 s = re.sub("><", " ", s)
1525 s = re.sub("<", "", s)
1526 s = re.sub(">", "", s)
1527 return s
1528
1529
1530def fixwordbreaks(root):
1531 # Make sure that Tk's double-click and next/previous word
1532 # operations use our definition of a word (i.e. an identifier)
1533 tk = root.tk
1534 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1535 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1536 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1537
1538
1539def test():
1540 root = Tk()
1541 fixwordbreaks(root)
1542 root.withdraw()
1543 if sys.argv[1:]:
1544 filename = sys.argv[1]
1545 else:
1546 filename = None
1547 edit = EditorWindow(root=root, filename=filename)
1548 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001549 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001550 root.mainloop()
1551 root.destroy()
1552
1553if __name__ == '__main__':
1554 test()