blob: 9ac70ac92a2a1579df935b01eb743498ba6acacd [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
David Scherer7aced172000-08-15 01:13:23 +00003import re
4import imp
Georg Brandl6634bf22008-05-20 07:13:37 +00005from Tkinter import *
6import tkSimpleDialog
7import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00008import webbrowser
Florent Xiclunad630c042010-04-02 07:24:52 +00009
10from idlelib.MultiCall import MultiCallCreator
11from idlelib import idlever
12from idlelib import WindowList
13from idlelib import SearchDialog
14from idlelib import GrepDialog
15from idlelib import ReplaceDialog
16from idlelib import PyParse
17from idlelib.configHandler import idleConf
18from idlelib import aboutDialog, textView, configDialog
19from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000020
21# The default tab setting for a Text widget, in average-width characters.
22TK_TABWIDTH_DEFAULT = 8
23
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000024def _sphinx_version():
25 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
26 major, minor, micro, level, serial = sys.version_info
27 release = '%s%s' % (major, minor)
28 if micro:
Benjamin Petersonfb234632009-06-13 15:42:23 +000029 release += '%s' % (micro,)
30 if level == 'candidate':
31 release += 'rc%s' % (serial,)
32 elif level != 'final':
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000033 release += '%s%s' % (level[0], serial)
34 return release
35
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000036def _find_module(fullname, path=None):
37 """Version of imp.find_module() that handles hierarchical module names"""
38
39 file = None
40 for tgt in fullname.split('.'):
41 if file is not None:
42 file.close() # close intermediate files
43 (file, filename, descr) = imp.find_module(tgt, path)
44 if descr[2] == imp.PY_SOURCE:
45 break # find but not load the source file
46 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000047 try:
48 path = module.__path__
49 except AttributeError:
50 raise ImportError, 'No source for module ' + module.__name__
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000051 return file, filename, descr
52
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000053class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +000054 from idlelib.Percolator import Percolator
55 from idlelib.ColorDelegator import ColorDelegator
56 from idlelib.UndoDelegator import UndoDelegator
57 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
58 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +000059 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +000060 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000061
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000062 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000063
64 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000065 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000066 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000067 if sys.platform.count('linux'):
68 # look for html docs in a couple of standard places
69 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
70 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
71 dochome = '/var/www/html/python/index.html'
72 else:
73 basepath = '/usr/share/doc/' # standard location
74 dochome = os.path.join(basepath, pyver,
75 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000076 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000077 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000078 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000079 if os.path.isfile(chmfile):
80 dochome = chmfile
Ronald Oussoren19302d92006-06-11 14:33:36 +000081 elif macosxSupport.runningAsOSXApp():
82 # documentation is stored inside the python framework
83 dochome = os.path.join(sys.prefix,
84 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000085 dochome = os.path.normpath(dochome)
86 if os.path.isfile(dochome):
87 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +000088 if sys.platform == 'darwin':
89 # Safari requires real file:-URLs
90 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000091 else:
Raymond Hettinger7b4c2be2009-01-20 10:46:23 +000092 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +000093 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000094 self.flist = flist
95 root = root or flist.root
96 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +000097 try:
98 sys.ps1
99 except AttributeError:
100 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000101 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000102 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000103 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000104 self.tkinter_vars = flist.vars
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000105 #self.top.instance_dict makes flist.inversedict avalable to
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000106 #configDialog.py so it can access all EditorWindow instaces
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000107 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000108 else:
109 self.tkinter_vars = {} # keys: Tkinter event names
110 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000111 self.top.instance_dict = {}
112 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000113 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000114 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000115 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000116 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000117 text_options = {
118 'name': 'text',
119 'padx': 5,
120 'wrap': 'none',
121 'width': self.width,
122 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
123 if TkVersion >= 8.5:
124 # Starting with tk 8.5 we have to set the new tabstyle option
125 # to 'wordprocessor' to achieve the same display of tabs as in
126 # older tk versions.
127 text_options['tabstyle'] = 'wordprocessor'
128 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000129 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000130
131 self.createmenubar()
132 self.apply_bindings()
133
134 self.top.protocol("WM_DELETE_WINDOW", self.close)
135 self.top.bind("<<close-window>>", self.close_event)
Ronald Oussoren17db4952006-07-23 09:41:09 +0000136 if macosxSupport.runningAsOSXApp():
137 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000138 text.bind('<<close-window>>', self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000139 text.bind("<<cut>>", self.cut)
140 text.bind("<<copy>>", self.copy)
141 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000142 text.bind("<<center-insert>>", self.center_insert_event)
143 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000144 text.bind("<<python-docs>>", self.python_docs)
145 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000146 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000147 text.bind("<<open-module>>", self.open_module)
148 text.bind("<<do-nothing>>", lambda event: "break")
149 text.bind("<<select-all>>", self.select_all)
150 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000151 text.bind("<<find>>", self.find_event)
152 text.bind("<<find-again>>", self.find_again_event)
153 text.bind("<<find-in-files>>", self.find_in_files_event)
154 text.bind("<<find-selection>>", self.find_selection_event)
155 text.bind("<<replace>>", self.replace_event)
156 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000157 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000158 text.bind("<<smart-backspace>>",self.smart_backspace_event)
159 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
160 text.bind("<<smart-indent>>",self.smart_indent_event)
161 text.bind("<<indent-region>>",self.indent_region_event)
162 text.bind("<<dedent-region>>",self.dedent_region_event)
163 text.bind("<<comment-region>>",self.comment_region_event)
164 text.bind("<<uncomment-region>>",self.uncomment_region_event)
165 text.bind("<<tabify-region>>",self.tabify_region_event)
166 text.bind("<<untabify-region>>",self.untabify_region_event)
167 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
168 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000169 text.bind("<Left>", self.move_at_edge_if_selection(0))
170 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000171 text.bind("<<del-word-left>>", self.del_word_left)
172 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000173 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000174
David Scherer7aced172000-08-15 01:13:23 +0000175 if flist:
176 flist.inversedict[self] = key
177 if key:
178 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000179 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000180 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
181 text.bind("<<open-class-browser>>", self.open_class_browser)
182 text.bind("<<open-path-browser>>", self.open_path_browser)
183
Steven M. Gava898a3652001-10-07 11:10:44 +0000184 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000185 vbar['command'] = text.yview
186 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000187 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000188 fontWeight = 'normal'
189 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000190 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000191 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
192 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
193 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000194 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
195 text.pack(side=TOP, fill=BOTH, expand=1)
196 text.focus_set()
197
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000198 # usetabs true -> literal tab characters are used by indent and
199 # dedent cmds, possibly mixed with spaces if
200 # indentwidth is not a multiple of tabwidth,
201 # which will cause Tabnanny to nag!
202 # false -> tab characters are converted to spaces by indent
203 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000204 # Although use-spaces=0 can be configured manually in config-main.def,
205 # configuration of tabs v. spaces is not supported in the configuration
206 # dialog. IDLE promotes the preferred Python indentation: use spaces!
207 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
208 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000209
210 # tabwidth is the display width of a literal tab character.
211 # CAUTION: telling Tk to use anything other than its default
212 # tab setting causes it to use an entirely different tabbing algorithm,
213 # treating tab stops as fixed distances from the left margin.
214 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000215 self.tabwidth = 8 # must remain 8 until Tk is fixed.
216
217 # indentwidth is the number of screen characters per indent level.
218 # The recommended Python indentation is four spaces.
219 self.indentwidth = self.tabwidth
220 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000221
222 # If context_use_ps1 is true, parsing searches back for a ps1 line;
223 # else searches for a popular (if, def, ...) Python stmt.
224 self.context_use_ps1 = False
225
226 # When searching backwards for a reliable place to begin parsing,
227 # first start num_context_lines[0] lines back, then
228 # num_context_lines[1] lines back if that didn't work, and so on.
229 # The last value should be huge (larger than the # of lines in a
230 # conceivable file).
231 # Making the initial values larger slows things down more often.
232 self.num_context_lines = 50, 500, 5000000
233
David Scherer7aced172000-08-15 01:13:23 +0000234 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000235
236 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)
241
242 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000243 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000244 io.set_filename_change_hook(self.filename_change_hook)
245
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000246 # Create the recent files submenu
247 self.recent_files_menu = Menu(self.menubar)
248 self.menudict['file'].insert_cascade(3, label='Recent Files',
249 underline=0,
250 menu=self.recent_files_menu)
251 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000252
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000253 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000254 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000255 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000256 io.loadfile(filename)
257 else:
258 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000259 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000260 self.saved_change_hook()
261
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000262 self.set_indentation_params(self.ispythonsource(filename))
263
David Scherer7aced172000-08-15 01:13:23 +0000264 self.load_extensions()
265
266 menu = self.menudict.get('windows')
267 if menu:
268 end = menu.index("end")
269 if end is None:
270 end = -1
271 if end >= 0:
272 menu.add_separator()
273 end = end + 1
274 self.wmenu_end = end
275 WindowList.register_callback(self.postwindowsmenu)
276
277 # Some abstractions so IDLE extensions are cross-IDE
278 self.askyesno = tkMessageBox.askyesno
279 self.askinteger = tkSimpleDialog.askinteger
280 self.showerror = tkMessageBox.showerror
281
Martin v. Löwis307021f2005-11-27 16:59:04 +0000282 def _filename_to_unicode(self, filename):
283 """convert filename to unicode in order to display it in Tk"""
284 if isinstance(filename, unicode) or not filename:
285 return filename
286 else:
287 try:
288 return filename.decode(self.filesystemencoding)
289 except UnicodeDecodeError:
290 # XXX
291 try:
292 return filename.decode(self.encoding)
293 except UnicodeDecodeError:
294 # byte-to-byte conversion
295 return filename.decode('iso8859-1')
296
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000297 def new_callback(self, event):
298 dirname, basename = self.io.defaultfilename()
299 self.flist.new(dirname)
300 return "break"
301
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000302 def home_callback(self, event):
303 if (event.state & 12) != 0 and event.keysym == "Home":
304 # state&1==shift, state&4==control, state&8==alt
305 return # <Modifier-Home>; fall back to class binding
306
307 if self.text.index("iomark") and \
308 self.text.compare("iomark", "<=", "insert lineend") and \
309 self.text.compare("insert linestart", "<=", "iomark"):
310 insertpt = int(self.text.index("iomark").split(".")[1])
311 else:
312 line = self.text.get("insert linestart", "insert lineend")
313 for insertpt in xrange(len(line)):
314 if line[insertpt] not in (' ','\t'):
315 break
316 else:
317 insertpt=len(line)
318
319 lineat = int(self.text.index("insert").split('.')[1])
320
321 if insertpt == lineat:
322 insertpt = 0
323
324 dest = "insert linestart+"+str(insertpt)+"c"
325
326 if (event.state&1) == 0:
327 # shift not pressed
328 self.text.tag_remove("sel", "1.0", "end")
329 else:
330 if not self.text.index("sel.first"):
331 self.text.mark_set("anchor","insert")
332
333 first = self.text.index(dest)
334 last = self.text.index("anchor")
335
336 if self.text.compare(first,">",last):
337 first,last = last,first
338
339 self.text.tag_remove("sel", "1.0", "end")
340 self.text.tag_add("sel", first, last)
341
342 self.text.mark_set("insert", dest)
343 self.text.see("insert")
344 return "break"
345
David Scherer7aced172000-08-15 01:13:23 +0000346 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000347 self.status_bar = self.MultiStatusBar(self.top)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000348 if macosxSupport.runningAsOSXApp():
349 # Insert some padding to avoid obscuring some of the statusbar
350 # by the resize widget.
351 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000352 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
353 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
354 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000355 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
356 self.text.event_add("<<set-line-and-column>>",
357 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000358 self.text.after_idle(self.set_line_and_column)
359
360 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000361 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000362 self.status_bar.set_label('column', 'Col: %s' % column)
363 self.status_bar.set_label('line', 'Ln: %s' % line)
364
David Scherer7aced172000-08-15 01:13:23 +0000365 menu_specs = [
366 ("file", "_File"),
367 ("edit", "_Edit"),
368 ("format", "F_ormat"),
369 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000370 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000371 ("windows", "_Windows"),
372 ("help", "_Help"),
373 ]
374
Ronald Oussoren19302d92006-06-11 14:33:36 +0000375 if macosxSupport.runningAsOSXApp():
376 del menu_specs[-3]
377 menu_specs[-2] = ("windows", "_Window")
378
379
David Scherer7aced172000-08-15 01:13:23 +0000380 def createmenubar(self):
381 mbar = self.menubar
382 self.menudict = menudict = {}
383 for name, label in self.menu_specs:
384 underline, label = prepstr(label)
385 menudict[name] = menu = Menu(mbar, name=name)
386 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000387
Ronald Oussorena97063a2009-03-04 21:35:05 +0000388 if macosxSupport.runningAsOSXApp():
Ronald Oussoren19302d92006-06-11 14:33:36 +0000389 # Insert the application menu
390 menudict['application'] = menu = Menu(mbar, name='apple')
391 mbar.add_cascade(label='IDLE', menu=menu)
392
David Scherer7aced172000-08-15 01:13:23 +0000393 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000394 self.base_helpmenu_length = self.menudict['help'].index(END)
395 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000396
397 def postwindowsmenu(self):
398 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000399 menu = self.menudict['windows']
400 end = menu.index("end")
401 if end is None:
402 end = -1
403 if end > self.wmenu_end:
404 menu.delete(self.wmenu_end+1, end)
405 WindowList.add_windows_to_menu(menu)
406
407 rmenu = None
408
409 def right_menu_event(self, event):
410 self.text.tag_remove("sel", "1.0", "end")
411 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
412 if not self.rmenu:
413 self.make_rmenu()
414 rmenu = self.rmenu
415 self.event = event
416 iswin = sys.platform[:3] == 'win'
417 if iswin:
418 self.text.config(cursor="arrow")
419 rmenu.tk_popup(event.x_root, event.y_root)
420 if iswin:
421 self.text.config(cursor="ibeam")
422
423 rmenu_specs = [
424 # ("Label", "<<virtual-event>>"), ...
425 ("Close", "<<close-window>>"), # Example
426 ]
427
428 def make_rmenu(self):
429 rmenu = Menu(self.text, tearoff=0)
430 for label, eventname in self.rmenu_specs:
431 def command(text=self.text, eventname=eventname):
432 text.event_generate(eventname)
433 rmenu.add_command(label=label, command=command)
434 self.rmenu = rmenu
435
436 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000437 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000438
Steven M. Gava3b55a892001-11-21 05:56:26 +0000439 def config_dialog(self, event=None):
440 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000441
David Scherer7aced172000-08-15 01:13:23 +0000442 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000443 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiserd5f49102007-10-04 02:53:07 +0000444 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000445
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000446 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000447 if sys.platform[:3] == 'win':
Steven M. Gava931625d2002-04-22 00:38:26 +0000448 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000449 else:
450 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000451 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000452
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000453 def cut(self,event):
454 self.text.event_generate("<<Cut>>")
455 return "break"
456
457 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000458 if not self.text.tag_ranges("sel"):
459 # There is no selection, so do nothing and maybe interrupt.
460 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000461 self.text.event_generate("<<Copy>>")
462 return "break"
463
464 def paste(self,event):
465 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000466 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000467 return "break"
468
David Scherer7aced172000-08-15 01:13:23 +0000469 def select_all(self, event=None):
470 self.text.tag_add("sel", "1.0", "end-1c")
471 self.text.mark_set("insert", "1.0")
472 self.text.see("insert")
473 return "break"
474
475 def remove_selection(self, event=None):
476 self.text.tag_remove("sel", "1.0", "end")
477 self.text.see("insert")
478
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000479 def move_at_edge_if_selection(self, edge_index):
480 """Cursor move begins at start or end of selection
481
482 When a left/right cursor key is pressed create and return to Tkinter a
483 function which causes a cursor move from the associated edge of the
484 selection.
485
486 """
487 self_text_index = self.text.index
488 self_text_mark_set = self.text.mark_set
489 edges_table = ("sel.first+1c", "sel.last-1c")
490 def move_at_edge(event):
491 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
492 try:
493 self_text_index("sel.first")
494 self_text_mark_set("insert", edges_table[edge_index])
495 except TclError:
496 pass
497 return move_at_edge
498
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000499 def del_word_left(self, event):
500 self.text.event_generate('<Meta-Delete>')
501 return "break"
502
503 def del_word_right(self, event):
504 self.text.event_generate('<Meta-d>')
505 return "break"
506
Steven M. Gavac5976402002-01-04 03:06:08 +0000507 def find_event(self, event):
508 SearchDialog.find(self.text)
509 return "break"
510
511 def find_again_event(self, event):
512 SearchDialog.find_again(self.text)
513 return "break"
514
515 def find_selection_event(self, event):
516 SearchDialog.find_selection(self.text)
517 return "break"
518
519 def find_in_files_event(self, event):
520 GrepDialog.grep(self.text, self.io, self.flist)
521 return "break"
522
523 def replace_event(self, event):
524 ReplaceDialog.replace(self.text)
525 return "break"
526
527 def goto_line_event(self, event):
528 text = self.text
529 lineno = tkSimpleDialog.askinteger("Goto",
530 "Go to line number:",parent=text)
531 if lineno is None:
532 return "break"
533 if lineno <= 0:
534 text.bell()
535 return "break"
536 text.mark_set("insert", "%d.0" % lineno)
537 text.see("insert")
538
David Scherer7aced172000-08-15 01:13:23 +0000539 def open_module(self, event=None):
540 # XXX Shouldn't this be in IOBinding or in FileList?
541 try:
542 name = self.text.get("sel.first", "sel.last")
543 except TclError:
544 name = ""
545 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000546 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000547 name = tkSimpleDialog.askstring("Module",
548 "Enter the name of a Python module\n"
549 "to search on sys.path and open:",
550 parent=self.text, initialvalue=name)
551 if name:
552 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000553 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000554 return
David Scherer7aced172000-08-15 01:13:23 +0000555 # XXX Ought to insert current file's directory in front of path
556 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000557 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000558 except (NameError, ImportError), msg:
559 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
560 return
561 if type != imp.PY_SOURCE:
562 tkMessageBox.showerror("Unsupported type",
563 "%s is not a source module" % name, parent=self.text)
564 return
565 if f:
566 f.close()
567 if self.flist:
568 self.flist.open(file)
569 else:
570 self.io.loadfile(file)
571
572 def open_class_browser(self, event=None):
573 filename = self.io.filename
574 if not filename:
575 tkMessageBox.showerror(
576 "No filename",
577 "This buffer has no associated filename",
578 master=self.text)
579 self.text.focus_set()
580 return None
581 head, tail = os.path.split(filename)
582 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000583 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000584 ClassBrowser.ClassBrowser(self.flist, base, [head])
585
586 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000587 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000588 PathBrowser.PathBrowser(self.flist)
589
590 def gotoline(self, lineno):
591 if lineno is not None and lineno > 0:
592 self.text.mark_set("insert", "%d.0" % lineno)
593 self.text.tag_remove("sel", "1.0", "end")
594 self.text.tag_add("sel", "insert", "insert +1l")
595 self.center()
596
597 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000598 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000599 return True
David Scherer7aced172000-08-15 01:13:23 +0000600 base, ext = os.path.splitext(os.path.basename(filename))
601 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000602 return True
David Scherer7aced172000-08-15 01:13:23 +0000603 try:
604 f = open(filename)
605 line = f.readline()
606 f.close()
607 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000608 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000609 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000610
611 def close_hook(self):
612 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000613 self.flist.unregister_maybe_terminate(self)
614 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000615
616 def set_close_hook(self, close_hook):
617 self.close_hook = close_hook
618
619 def filename_change_hook(self):
620 if self.flist:
621 self.flist.filename_changed_edit(self)
622 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000623 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000624 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000625
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000626 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000627 if self.color:
628 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000629 if self.ispythonsource(self.io.filename):
630 self.color = self.ColorDelegator()
631 # can add more colorizers here...
632 if self.color:
633 self.per.removefilter(self.undo)
634 self.per.insertfilter(self.color)
635 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000636
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000637 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000638 if not self.color:
639 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000640 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000641 self.per.removefilter(self.color)
642 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000643
Steven M. Gavab77d3432002-03-02 07:16:21 +0000644 def ResetColorizer(self):
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000645 "Update the colour theme"
646 # Called from self.filename_change_hook and from configDialog.py
647 self._rmcolorizer()
648 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000649 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000650 normal_colors = idleConf.GetHighlight(theme, 'normal')
651 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
652 select_colors = idleConf.GetHighlight(theme, 'hilite')
653 self.text.config(
654 foreground=normal_colors['foreground'],
655 background=normal_colors['background'],
656 insertbackground=cursor_color,
657 selectforeground=select_colors['foreground'],
658 selectbackground=select_colors['background'],
659 )
David Scherer7aced172000-08-15 01:13:23 +0000660
Steven M. Gavab1585412002-03-12 00:21:56 +0000661 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000662 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000663 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000664 fontWeight='normal'
665 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
666 fontWeight='bold'
667 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
668 idleConf.GetOption('main','EditorWindow','font-size'),
669 fontWeight))
670
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000671 def RemoveKeybindings(self):
672 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000673 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000674 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000675 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000676 self.text.event_delete(event, *keylist)
677 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 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000681 self.text.event_delete(event, *keylist)
682
683 def ApplyKeybindings(self):
684 "Update the keybindings after they are changed"
685 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000686 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000687 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000688 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000689 xkeydefs = idleConf.GetExtensionBindings(extensionName)
690 if xkeydefs:
691 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000692 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000693 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000694 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000695 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000696 for item in menu[1]:
697 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000698 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000699 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000700 menu = self.menudict[menubarItem]
701 end = menu.index(END) + 1
702 for index in range(0, end):
703 if menu.type(index) == 'command':
704 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000705 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000706 itemName = menu.entrycget(index, 'label')
707 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000708 if menubarItem in menuEventDict:
709 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000710 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000711 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000712 accel = get_accelerator(keydefs, event)
713 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000714
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000715 def set_notabs_indentwidth(self):
716 "Update the indentwidth if changed and not using tabs in this window"
717 # Called from configDialog.py
718 if not self.usetabs:
719 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
720 type='int')
721
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000722 def reset_help_menu_entries(self):
723 "Update the additional help entries on the Help menu"
724 help_list = idleConf.GetAllExtraHelpSourcesList()
725 helpmenu = self.menudict['help']
726 # first delete the extra help entries, if any
727 helpmenu_length = helpmenu.index(END)
728 if helpmenu_length > self.base_helpmenu_length:
729 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
730 # then rebuild them
731 if help_list:
732 helpmenu.add_separator()
733 for entry in help_list:
734 cmd = self.__extra_help_callback(entry[1])
735 helpmenu.add_command(label=entry[0], command=cmd)
736 # and update the menu dictionary
737 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000738
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000739 def __extra_help_callback(self, helpfile):
740 "Create a callback with the helpfile value frozen at definition time"
741 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000742 if not helpfile.startswith(('www', 'http')):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000743 url = os.path.normpath(helpfile)
744 if sys.platform[:3] == 'win':
745 os.startfile(helpfile)
746 else:
747 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000748 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000749
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000750 def update_recent_files_list(self, new_file=None):
751 "Load and update the recent files list and menus"
752 rf_list = []
753 if os.path.exists(self.recent_files_path):
754 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000755 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000756 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000757 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000758 rf_list_file.close()
759 if new_file:
760 new_file = os.path.abspath(new_file) + '\n'
761 if new_file in rf_list:
762 rf_list.remove(new_file) # move to top
763 rf_list.insert(0, new_file)
764 # clean and save the recent files list
765 bad_paths = []
766 for path in rf_list:
767 if '\0' in path or not os.path.exists(path[0:-1]):
768 bad_paths.append(path)
769 rf_list = [path for path in rf_list if path not in bad_paths]
770 ulchars = "1234567890ABCDEFGHIJK"
771 rf_list = rf_list[0:len(ulchars)]
772 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000773 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000774 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000775 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000776 rf_file.close()
777 # for each edit window instance, construct the recent files menu
778 for instance in self.top.instance_dict.keys():
779 menu = instance.recent_files_menu
780 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000781 for i, file_name in enumerate(rf_list):
782 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000783 # make unicode string to display non-ASCII chars correctly
784 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000785 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000786 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000787 command=callback,
788 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000789
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000790 def __recent_file_callback(self, file_name):
791 def open_recent_file(fn_closure=file_name):
792 self.io.open(editFile=fn_closure)
793 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000794
David Scherer7aced172000-08-15 01:13:23 +0000795 def saved_change_hook(self):
796 short = self.short_title()
797 long = self.long_title()
798 if short and long:
799 title = short + " - " + long
800 elif short:
801 title = short
802 elif long:
803 title = long
804 else:
805 title = "Untitled"
806 icon = short or long or title
807 if not self.get_saved():
808 title = "*%s*" % title
809 icon = "*%s" % icon
810 self.top.wm_title(title)
811 self.top.wm_iconname(icon)
812
813 def get_saved(self):
814 return self.undo.get_saved()
815
816 def set_saved(self, flag):
817 self.undo.set_saved(flag)
818
819 def reset_undo(self):
820 self.undo.reset_undo()
821
822 def short_title(self):
823 filename = self.io.filename
824 if filename:
825 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000826 # return unicode string to display non-ASCII chars correctly
827 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000828
829 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000830 # return unicode string to display non-ASCII chars correctly
831 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000832
833 def center_insert_event(self, event):
834 self.center()
835
836 def center(self, mark="insert"):
837 text = self.text
838 top, bot = self.getwindowlines()
839 lineno = self.getlineno(mark)
840 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000841 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000842 text.yview(float(newtop))
843
844 def getwindowlines(self):
845 text = self.text
846 top = self.getlineno("@0,0")
847 bot = self.getlineno("@0,65535")
848 if top == bot and text.winfo_height() == 1:
849 # Geometry manager hasn't run yet
850 height = int(text['height'])
851 bot = top + height - 1
852 return top, bot
853
854 def getlineno(self, mark="insert"):
855 text = self.text
856 return int(float(text.index(mark)))
857
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000858 def get_geometry(self):
859 "Return (width, height, x, y)"
860 geom = self.top.wm_geometry()
861 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
862 tuple = (map(int, m.groups()))
863 return tuple
864
David Scherer7aced172000-08-15 01:13:23 +0000865 def close_event(self, event):
866 self.close()
867
868 def maybesave(self):
869 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000870 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000871 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000872 self.top.deiconify()
873 self.top.lower()
874 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000875 return self.io.maybesave()
876
877 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000878 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +0000879 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000880 self._close()
881 return reply
882
883 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000884 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000885 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000886 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000887 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000888 self.io.close()
889 self.io = None
890 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000891 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000892 self.color.close(False)
893 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000894 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000895 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000896 self.per.close()
897 self.per = None
898 self.top.destroy()
899 if self.close_hook:
900 # unless override: unregister from flist, terminate if last window
901 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000902
903 def load_extensions(self):
904 self.extensions = {}
905 self.load_standard_extensions()
906
907 def unload_extensions(self):
908 for ins in self.extensions.values():
909 if hasattr(ins, "close"):
910 ins.close()
911 self.extensions = {}
912
913 def load_standard_extensions(self):
914 for name in self.get_standard_extension_names():
915 try:
916 self.load_extension(name)
917 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000918 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +0000919 import traceback
920 traceback.print_exc()
921
922 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000923 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000924
925 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000926 try:
927 mod = __import__(name, globals(), locals(), [])
928 except ImportError:
929 print "\nFailed to import extension: ", name
930 return
David Scherer7aced172000-08-15 01:13:23 +0000931 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000932 keydefs = idleConf.GetExtensionBindings(name)
933 if hasattr(cls, "menudefs"):
934 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000935 ins = cls(self)
936 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000937 if keydefs:
938 self.apply_bindings(keydefs)
939 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000940 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000941 while methodname[:1] == '<':
942 methodname = methodname[1:]
943 while methodname[-1:] == '>':
944 methodname = methodname[:-1]
945 methodname = methodname + "_event"
946 if hasattr(ins, methodname):
947 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000948
949 def apply_bindings(self, keydefs=None):
950 if keydefs is None:
951 keydefs = self.Bindings.default_keydefs
952 text = self.text
953 text.keydefs = keydefs
954 for event, keylist in keydefs.items():
955 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000956 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000957
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000958 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000959 """Add appropriate entries to the menus and submenus
960
961 Menus that are absent or None in self.menudict are ignored.
962 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000963 if menudefs is None:
964 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000965 if keydefs is None:
966 keydefs = self.Bindings.default_keydefs
967 menudict = self.menudict
968 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000969 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000970 menu = menudict.get(mname)
971 if not menu:
972 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000973 for entry in entrylist:
974 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000975 menu.add_separator()
976 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000977 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000978 checkbutton = (label[:1] == '!')
979 if checkbutton:
980 label = label[1:]
981 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000982 accelerator = get_accelerator(keydefs, eventname)
983 def command(text=text, eventname=eventname):
984 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000985 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000986 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000987 menu.add_checkbutton(label=label, underline=underline,
988 command=command, accelerator=accelerator,
989 variable=var)
990 else:
991 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000992 command=command,
993 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000994
995 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000996 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +0000997 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000998 value = var.get()
999 return value
1000 else:
1001 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001002
1003 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001004 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001005 if var:
1006 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001007 else:
1008 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001009
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001010 def get_var_obj(self, name, vartype=None):
1011 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001012 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001013 # create a Tkinter variable object with self.text as master:
1014 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001015 return var
1016
1017 # Tk implementations of "virtual text methods" -- each platform
1018 # reusing IDLE's support code needs to define these for its GUI's
1019 # flavor of widget.
1020
1021 # Is character at text_index in a Python string? Return 0 for
1022 # "guaranteed no", true for anything else. This info is expensive
1023 # to compute ab initio, but is probably already known by the
1024 # platform's colorizer.
1025
1026 def is_char_in_string(self, text_index):
1027 if self.color:
1028 # Return true iff colorizer hasn't (re)gotten this far
1029 # yet, or the character is tagged as being in a string
1030 return self.text.tag_prevrange("TODO", text_index) or \
1031 "STRING" in self.text.tag_names(text_index)
1032 else:
1033 # The colorizer is missing: assume the worst
1034 return 1
1035
1036 # If a selection is defined in the text widget, return (start,
1037 # end) as Tkinter text indices, otherwise return (None, None)
1038 def get_selection_indices(self):
1039 try:
1040 first = self.text.index("sel.first")
1041 last = self.text.index("sel.last")
1042 return first, last
1043 except TclError:
1044 return None, None
1045
1046 # Return the text widget's current view of what a tab stop means
1047 # (equivalent width in spaces).
1048
1049 def get_tabwidth(self):
1050 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1051 return int(current)
1052
1053 # Set the text widget's current view of what a tab stop means.
1054
1055 def set_tabwidth(self, newtabwidth):
1056 text = self.text
1057 if self.get_tabwidth() != newtabwidth:
1058 pixels = text.tk.call("font", "measure", text["font"],
1059 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001060 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001061 text.configure(tabs=pixels)
1062
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001063 # If ispythonsource and guess are true, guess a good value for
1064 # indentwidth based on file content (if possible), and if
1065 # indentwidth != tabwidth set usetabs false.
1066 # In any case, adjust the Text widget's view of what a tab
1067 # character means.
1068
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001069 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001070 if guess and ispythonsource:
1071 i = self.guess_indent()
1072 if 2 <= i <= 8:
1073 self.indentwidth = i
1074 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001075 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001076 self.set_tabwidth(self.tabwidth)
1077
1078 def smart_backspace_event(self, event):
1079 text = self.text
1080 first, last = self.get_selection_indices()
1081 if first and last:
1082 text.delete(first, last)
1083 text.mark_set("insert", first)
1084 return "break"
1085 # Delete whitespace left, until hitting a real char or closest
1086 # preceding virtual tab stop.
1087 chars = text.get("insert linestart", "insert")
1088 if chars == '':
1089 if text.compare("insert", ">", "1.0"):
1090 # easy: delete preceding newline
1091 text.delete("insert-1c")
1092 else:
1093 text.bell() # at start of buffer
1094 return "break"
1095 if chars[-1] not in " \t":
1096 # easy: delete preceding real char
1097 text.delete("insert-1c")
1098 return "break"
1099 # Ick. It may require *inserting* spaces if we back up over a
1100 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001101 tabwidth = self.tabwidth
1102 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001103 assert have > 0
1104 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001105 # Debug prompt is multilined....
1106 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001107 ncharsdeleted = 0
1108 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001109 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001110 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001111 chars = chars[:-1]
1112 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001113 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001114 if have <= want or chars[-1] not in " \t":
1115 break
1116 text.undo_block_start()
1117 text.delete("insert-%dc" % ncharsdeleted, "insert")
1118 if have < want:
1119 text.insert("insert", ' ' * (want - have))
1120 text.undo_block_stop()
1121 return "break"
1122
1123 def smart_indent_event(self, event):
1124 # if intraline selection:
1125 # delete it
1126 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001127 # do indent-region
1128 # else:
1129 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001130 text = self.text
1131 first, last = self.get_selection_indices()
1132 text.undo_block_start()
1133 try:
1134 if first and last:
1135 if index2line(first) != index2line(last):
1136 return self.indent_region_event(event)
1137 text.delete(first, last)
1138 text.mark_set("insert", first)
1139 prefix = text.get("insert linestart", "insert")
1140 raw, effective = classifyws(prefix, self.tabwidth)
1141 if raw == len(prefix):
1142 # only whitespace to the left
1143 self.reindent_to(effective + self.indentwidth)
1144 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001145 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001146 if self.usetabs:
1147 pad = '\t'
1148 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001149 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001150 n = self.indentwidth
1151 pad = ' ' * (n - effective % n)
1152 text.insert("insert", pad)
1153 text.see("insert")
1154 return "break"
1155 finally:
1156 text.undo_block_stop()
1157
1158 def newline_and_indent_event(self, event):
1159 text = self.text
1160 first, last = self.get_selection_indices()
1161 text.undo_block_start()
1162 try:
1163 if first and last:
1164 text.delete(first, last)
1165 text.mark_set("insert", first)
1166 line = text.get("insert linestart", "insert")
1167 i, n = 0, len(line)
1168 while i < n and line[i] in " \t":
1169 i = i+1
1170 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001171 # the cursor is in or at leading indentation in a continuation
1172 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001173 text.insert("insert linestart", '\n')
1174 return "break"
1175 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001176 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001177 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001178 last_line_of_prompt = sys.ps1.split('\n')[-1]
1179 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001180 line = line[:-1]
1181 i = i+1
1182 if i:
1183 text.delete("insert - %d chars" % i, "insert")
1184 # strip whitespace after insert point
1185 while text.get("insert") in " \t":
1186 text.delete("insert")
1187 # start new line
1188 text.insert("insert", '\n')
1189
1190 # adjust indentation for continuations and block
1191 # open/close first need to find the last stmt
1192 lno = index2line(text.index('insert'))
1193 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001194 if not self.context_use_ps1:
1195 for context in self.num_context_lines:
1196 startat = max(lno - context, 1)
1197 startatindex = `startat` + ".0"
1198 rawtext = text.get(startatindex, "insert")
1199 y.set_str(rawtext)
1200 bod = y.find_good_parse_start(
1201 self.context_use_ps1,
1202 self._build_char_in_string_func(startatindex))
1203 if bod is not None or startat == 1:
1204 break
1205 y.set_lo(bod or 0)
1206 else:
1207 r = text.tag_prevrange("console", "insert")
1208 if r:
1209 startatindex = r[1]
1210 else:
1211 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001212 rawtext = text.get(startatindex, "insert")
1213 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001214 y.set_lo(0)
1215
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001216 c = y.get_continuation_type()
1217 if c != PyParse.C_NONE:
1218 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001219 if c == PyParse.C_STRING_FIRST_LINE:
1220 # after the first line of a string; do not indent at all
1221 pass
1222 elif c == PyParse.C_STRING_NEXT_LINES:
1223 # inside a string which started before this line;
1224 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001225 text.insert("insert", indent)
1226 elif c == PyParse.C_BRACKET:
1227 # line up with the first (if any) element of the
1228 # last open bracket structure; else indent one
1229 # level beyond the indent of the line with the
1230 # last open bracket
1231 self.reindent_to(y.compute_bracket_indent())
1232 elif c == PyParse.C_BACKSLASH:
1233 # if more than one line in this stmt already, just
1234 # mimic the current indent; else if initial line
1235 # has a start on an assignment stmt, indent to
1236 # beyond leftmost =; else to beyond first chunk of
1237 # non-whitespace on initial line
1238 if y.get_num_lines_in_stmt() > 1:
1239 text.insert("insert", indent)
1240 else:
1241 self.reindent_to(y.compute_backslash_indent())
1242 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001243 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001244 return "break"
1245
1246 # This line starts a brand new stmt; indent relative to
1247 # indentation of initial line of closest preceding
1248 # interesting stmt.
1249 indent = y.get_base_indent_string()
1250 text.insert("insert", indent)
1251 if y.is_block_opener():
1252 self.smart_indent_event(event)
1253 elif indent and y.is_block_closer():
1254 self.smart_backspace_event(event)
1255 return "break"
1256 finally:
1257 text.see("insert")
1258 text.undo_block_stop()
1259
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001260 # Our editwin provides a is_char_in_string function that works
1261 # with a Tk text index, but PyParse only knows about offsets into
1262 # a string. This builds a function for PyParse that accepts an
1263 # offset.
1264
1265 def _build_char_in_string_func(self, startindex):
1266 def inner(offset, _startindex=startindex,
1267 _icis=self.is_char_in_string):
1268 return _icis(_startindex + "+%dc" % offset)
1269 return inner
1270
1271 def indent_region_event(self, event):
1272 head, tail, chars, lines = self.get_region()
1273 for pos in range(len(lines)):
1274 line = lines[pos]
1275 if line:
1276 raw, effective = classifyws(line, self.tabwidth)
1277 effective = effective + self.indentwidth
1278 lines[pos] = self._make_blanks(effective) + line[raw:]
1279 self.set_region(head, tail, chars, lines)
1280 return "break"
1281
1282 def dedent_region_event(self, event):
1283 head, tail, chars, lines = self.get_region()
1284 for pos in range(len(lines)):
1285 line = lines[pos]
1286 if line:
1287 raw, effective = classifyws(line, self.tabwidth)
1288 effective = max(effective - self.indentwidth, 0)
1289 lines[pos] = self._make_blanks(effective) + line[raw:]
1290 self.set_region(head, tail, chars, lines)
1291 return "break"
1292
1293 def comment_region_event(self, event):
1294 head, tail, chars, lines = self.get_region()
1295 for pos in range(len(lines) - 1):
1296 line = lines[pos]
1297 lines[pos] = '##' + line
1298 self.set_region(head, tail, chars, lines)
1299
1300 def uncomment_region_event(self, event):
1301 head, tail, chars, lines = self.get_region()
1302 for pos in range(len(lines)):
1303 line = lines[pos]
1304 if not line:
1305 continue
1306 if line[:2] == '##':
1307 line = line[2:]
1308 elif line[:1] == '#':
1309 line = line[1:]
1310 lines[pos] = line
1311 self.set_region(head, tail, chars, lines)
1312
1313 def tabify_region_event(self, event):
1314 head, tail, chars, lines = self.get_region()
1315 tabwidth = self._asktabwidth()
1316 for pos in range(len(lines)):
1317 line = lines[pos]
1318 if line:
1319 raw, effective = classifyws(line, tabwidth)
1320 ntabs, nspaces = divmod(effective, tabwidth)
1321 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1322 self.set_region(head, tail, chars, lines)
1323
1324 def untabify_region_event(self, event):
1325 head, tail, chars, lines = self.get_region()
1326 tabwidth = self._asktabwidth()
1327 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001328 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001329 self.set_region(head, tail, chars, lines)
1330
1331 def toggle_tabs_event(self, event):
1332 if self.askyesno(
1333 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001334 "Turn tabs " + ("on", "off")[self.usetabs] +
1335 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001336 ("will be", "remains at")[self.usetabs] + " 8." +
1337 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001338 parent=self.text):
1339 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001340 # Try to prevent inconsistent indentation.
1341 # User must change indent width manually after using tabs.
1342 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001343 return "break"
1344
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001345 # XXX this isn't bound to anything -- see tabwidth comments
1346## def change_tabwidth_event(self, event):
1347## new = self._asktabwidth()
1348## if new != self.tabwidth:
1349## self.tabwidth = new
1350## self.set_indentation_params(0, guess=0)
1351## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001352
1353 def change_indentwidth_event(self, event):
1354 new = self.askinteger(
1355 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001356 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001357 parent=self.text,
1358 initialvalue=self.indentwidth,
1359 minvalue=2,
1360 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001361 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001362 self.indentwidth = new
1363 return "break"
1364
1365 def get_region(self):
1366 text = self.text
1367 first, last = self.get_selection_indices()
1368 if first and last:
1369 head = text.index(first + " linestart")
1370 tail = text.index(last + "-1c lineend +1c")
1371 else:
1372 head = text.index("insert linestart")
1373 tail = text.index("insert lineend +1c")
1374 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001375 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001376 return head, tail, chars, lines
1377
1378 def set_region(self, head, tail, chars, lines):
1379 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001380 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001381 if newchars == chars:
1382 text.bell()
1383 return
1384 text.tag_remove("sel", "1.0", "end")
1385 text.mark_set("insert", head)
1386 text.undo_block_start()
1387 text.delete(head, tail)
1388 text.insert(head, newchars)
1389 text.undo_block_stop()
1390 text.tag_add("sel", head, "insert")
1391
1392 # Make string that displays as n leading blanks.
1393
1394 def _make_blanks(self, n):
1395 if self.usetabs:
1396 ntabs, nspaces = divmod(n, self.tabwidth)
1397 return '\t' * ntabs + ' ' * nspaces
1398 else:
1399 return ' ' * n
1400
1401 # Delete from beginning of line to insert point, then reinsert
1402 # column logical (meaning use tabs if appropriate) spaces.
1403
1404 def reindent_to(self, column):
1405 text = self.text
1406 text.undo_block_start()
1407 if text.compare("insert linestart", "!=", "insert"):
1408 text.delete("insert linestart", "insert")
1409 if column:
1410 text.insert("insert", self._make_blanks(column))
1411 text.undo_block_stop()
1412
1413 def _asktabwidth(self):
1414 return self.askinteger(
1415 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001416 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001417 parent=self.text,
1418 initialvalue=self.indentwidth,
1419 minvalue=2,
1420 maxvalue=16) or self.tabwidth
1421
1422 # Guess indentwidth from text content.
1423 # Return guessed indentwidth. This should not be believed unless
1424 # it's in a reasonable range (e.g., it will be 0 if no indented
1425 # blocks are found).
1426
1427 def guess_indent(self):
1428 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1429 if opener and indented:
1430 raw, indentsmall = classifyws(opener, self.tabwidth)
1431 raw, indentlarge = classifyws(indented, self.tabwidth)
1432 else:
1433 indentsmall = indentlarge = 0
1434 return indentlarge - indentsmall
1435
1436# "line.col" -> line, as an int
1437def index2line(index):
1438 return int(float(index))
1439
1440# Look at the leading whitespace in s.
1441# Return pair (# of leading ws characters,
1442# effective # of leading blanks after expanding
1443# tabs to width tabwidth)
1444
1445def classifyws(s, tabwidth):
1446 raw = effective = 0
1447 for ch in s:
1448 if ch == ' ':
1449 raw = raw + 1
1450 effective = effective + 1
1451 elif ch == '\t':
1452 raw = raw + 1
1453 effective = (effective // tabwidth + 1) * tabwidth
1454 else:
1455 break
1456 return raw, effective
1457
1458import tokenize
1459_tokenize = tokenize
1460del tokenize
1461
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001462class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001463
1464 # .run() chews over the Text widget, looking for a block opener
1465 # and the stmt following it. Returns a pair,
1466 # (line containing block opener, line containing stmt)
1467 # Either or both may be None.
1468
1469 def __init__(self, text, tabwidth):
1470 self.text = text
1471 self.tabwidth = tabwidth
1472 self.i = self.finished = 0
1473 self.blkopenline = self.indentedline = None
1474
1475 def readline(self):
1476 if self.finished:
1477 return ""
1478 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001479 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001480 if self.text.compare(mark, ">=", "end"):
1481 return ""
1482 return self.text.get(mark, mark + " lineend+1c")
1483
1484 def tokeneater(self, type, token, start, end, line,
1485 INDENT=_tokenize.INDENT,
1486 NAME=_tokenize.NAME,
1487 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1488 if self.finished:
1489 pass
1490 elif type == NAME and token in OPENERS:
1491 self.blkopenline = line
1492 elif type == INDENT and self.blkopenline:
1493 self.indentedline = line
1494 self.finished = 1
1495
1496 def run(self):
1497 save_tabsize = _tokenize.tabsize
1498 _tokenize.tabsize = self.tabwidth
1499 try:
1500 try:
1501 _tokenize.tokenize(self.readline, self.tokeneater)
1502 except _tokenize.TokenError:
1503 # since we cut off the tokenizer early, we can trigger
1504 # spurious errors
1505 pass
1506 finally:
1507 _tokenize.tabsize = save_tabsize
1508 return self.blkopenline, self.indentedline
1509
1510### end autoindent code ###
1511
David Scherer7aced172000-08-15 01:13:23 +00001512def prepstr(s):
1513 # Helper to extract the underscore from a string, e.g.
1514 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001515 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001516 if i >= 0:
1517 s = s[:i] + s[i+1:]
1518 return i, s
1519
1520
1521keynames = {
1522 'bracketleft': '[',
1523 'bracketright': ']',
1524 'slash': '/',
1525}
1526
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001527def get_accelerator(keydefs, eventname):
1528 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001529 if not keylist:
1530 return ""
1531 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001532 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001533 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1534 s = re.sub("Key-", "", s)
1535 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1536 s = re.sub("Control-", "Ctrl-", s)
1537 s = re.sub("-", "+", s)
1538 s = re.sub("><", " ", s)
1539 s = re.sub("<", "", s)
1540 s = re.sub(">", "", s)
1541 return s
1542
1543
1544def fixwordbreaks(root):
1545 # Make sure that Tk's double-click and next/previous word
1546 # operations use our definition of a word (i.e. an identifier)
1547 tk = root.tk
1548 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1549 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1550 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1551
1552
1553def test():
1554 root = Tk()
1555 fixwordbreaks(root)
1556 root.withdraw()
1557 if sys.argv[1:]:
1558 filename = sys.argv[1]
1559 else:
1560 filename = None
1561 edit = EditorWindow(root=root, filename=filename)
1562 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001563 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001564 root.mainloop()
1565 root.destroy()
1566
1567if __name__ == '__main__':
1568 test()