blob: d77f0a42ee66b4c393d84551df4e3bf615150886 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
David Scherer7aced172000-08-15 01:13:23 +00003import re
Guido van Rossum33d26892007-08-05 15:29:28 +00004import string
David Scherer7aced172000-08-15 01:13:23 +00005import imp
Georg Brandl14fc4272008-05-17 18:39:55 +00006from tkinter import *
7import tkinter.simpledialog as tkSimpleDialog
8import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +00009import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000010import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000011
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000012from idlelib.MultiCall import MultiCallCreator
13from idlelib import idlever
14from idlelib import WindowList
15from idlelib import SearchDialog
16from idlelib import GrepDialog
17from idlelib import ReplaceDialog
18from idlelib import PyParse
19from idlelib.configHandler import idleConf
20from idlelib import aboutDialog, textView, configDialog
21from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000022
23# The default tab setting for a Text widget, in average-width characters.
24TK_TABWIDTH_DEFAULT = 8
25
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000026def _sphinx_version():
27 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
28 major, minor, micro, level, serial = sys.version_info
29 release = '%s%s' % (major, minor)
30 if micro:
Benjamin Petersonb48f6342009-06-22 19:36:31 +000031 release += '%s' % (micro,)
32 if level == 'candidate':
33 release += 'rc%s' % (serial,)
34 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000035 release += '%s%s' % (level[0], serial)
36 return release
37
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000038def _find_module(fullname, path=None):
39 """Version of imp.find_module() that handles hierarchical module names"""
40
41 file = None
42 for tgt in fullname.split('.'):
43 if file is not None:
44 file.close() # close intermediate files
45 (file, filename, descr) = imp.find_module(tgt, path)
46 if descr[2] == imp.PY_SOURCE:
47 break # find but not load the source file
48 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000049 try:
50 path = module.__path__
51 except AttributeError:
Kurt B. Kaiserad667422007-08-23 01:06:15 +000052 raise ImportError('No source for module ' + module.__name__)
Raymond Hettingerf6445e82011-04-12 18:30:14 -070053 if descr[2] != imp.PY_SOURCE:
54 # If all of the above fails and didn't raise an exception,fallback
55 # to a straight import which can find __init__.py in a package.
56 m = __import__(fullname)
57 try:
58 filename = m.__file__
59 except AttributeError:
60 pass
61 else:
62 file = None
Raymond Hettinger2df393c2011-04-12 18:57:55 -070063 descr = os.path.splitext(filename)[1], None, imp.PY_SOURCE
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000064 return file, filename, descr
65
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000066class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000067 from idlelib.Percolator import Percolator
68 from idlelib.ColorDelegator import ColorDelegator
69 from idlelib.UndoDelegator import UndoDelegator
70 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
71 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000072 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000073 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000074
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000075 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000076
77 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000078 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000079 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000080 if sys.platform.count('linux'):
81 # look for html docs in a couple of standard places
82 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
83 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
84 dochome = '/var/www/html/python/index.html'
85 else:
86 basepath = '/usr/share/doc/' # standard location
87 dochome = os.path.join(basepath, pyver,
88 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000089 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000090 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000091 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000092 if os.path.isfile(chmfile):
93 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +000094 elif macosxSupport.runningAsOSXApp():
95 # documentation is stored inside the python framework
96 dochome = os.path.join(sys.prefix,
97 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000098 dochome = os.path.normpath(dochome)
99 if os.path.isfile(dochome):
100 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000101 if sys.platform == 'darwin':
102 # Safari requires real file:-URLs
103 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000104 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +0000105 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000106 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000107 self.flist = flist
108 root = root or flist.root
109 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000110 try:
111 sys.ps1
112 except AttributeError:
113 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000114 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000115 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000116 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000117 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200118 #self.top.instance_dict makes flist.inversedict available to
119 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000120 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000121 else:
122 self.tkinter_vars = {} # keys: Tkinter event names
123 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000124 self.top.instance_dict = {}
125 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000126 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000127 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000128 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000129 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000130 text_options = {
131 'name': 'text',
132 'padx': 5,
133 'wrap': 'none',
134 'width': self.width,
135 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
136 if TkVersion >= 8.5:
137 # Starting with tk 8.5 we have to set the new tabstyle option
138 # to 'wordprocessor' to achieve the same display of tabs as in
139 # older tk versions.
140 text_options['tabstyle'] = 'wordprocessor'
141 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000142 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000143
144 self.createmenubar()
145 self.apply_bindings()
146
147 self.top.protocol("WM_DELETE_WINDOW", self.close)
148 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000149 if macosxSupport.runningAsOSXApp():
150 # Command-W on editorwindows doesn't work without this.
151 text.bind('<<close-window>>', self.close_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000152 # Some OS X systems have only one mouse button,
153 # so use control-click for pulldown menus there.
154 # (Note, AquaTk defines <2> as the right button if
155 # present and the Tk Text widget already binds <2>.)
156 text.bind("<Control-Button-1>",self.right_menu_event)
157 else:
158 # Elsewhere, use right-click for pulldown menus.
159 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000160 text.bind("<<cut>>", self.cut)
161 text.bind("<<copy>>", self.copy)
162 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000163 text.bind("<<center-insert>>", self.center_insert_event)
164 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000165 text.bind("<<python-docs>>", self.python_docs)
166 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000167 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000168 text.bind("<<open-module>>", self.open_module)
169 text.bind("<<do-nothing>>", lambda event: "break")
170 text.bind("<<select-all>>", self.select_all)
171 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000172 text.bind("<<find>>", self.find_event)
173 text.bind("<<find-again>>", self.find_again_event)
174 text.bind("<<find-in-files>>", self.find_in_files_event)
175 text.bind("<<find-selection>>", self.find_selection_event)
176 text.bind("<<replace>>", self.replace_event)
177 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000178 text.bind("<<smart-backspace>>",self.smart_backspace_event)
179 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
180 text.bind("<<smart-indent>>",self.smart_indent_event)
181 text.bind("<<indent-region>>",self.indent_region_event)
182 text.bind("<<dedent-region>>",self.dedent_region_event)
183 text.bind("<<comment-region>>",self.comment_region_event)
184 text.bind("<<uncomment-region>>",self.uncomment_region_event)
185 text.bind("<<tabify-region>>",self.tabify_region_event)
186 text.bind("<<untabify-region>>",self.untabify_region_event)
187 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
188 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000189 text.bind("<Left>", self.move_at_edge_if_selection(0))
190 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000191 text.bind("<<del-word-left>>", self.del_word_left)
192 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000193 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000194
David Scherer7aced172000-08-15 01:13:23 +0000195 if flist:
196 flist.inversedict[self] = key
197 if key:
198 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000199 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000200 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
201 text.bind("<<open-class-browser>>", self.open_class_browser)
202 text.bind("<<open-path-browser>>", self.open_path_browser)
203
Steven M. Gava898a3652001-10-07 11:10:44 +0000204 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000205 vbar['command'] = text.yview
206 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000207 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000208 fontWeight = 'normal'
209 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000210 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000211 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
212 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
213 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000214 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
215 text.pack(side=TOP, fill=BOTH, expand=1)
216 text.focus_set()
217
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000218 # usetabs true -> literal tab characters are used by indent and
219 # dedent cmds, possibly mixed with spaces if
220 # indentwidth is not a multiple of tabwidth,
221 # which will cause Tabnanny to nag!
222 # false -> tab characters are converted to spaces by indent
223 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000224 # Although use-spaces=0 can be configured manually in config-main.def,
225 # configuration of tabs v. spaces is not supported in the configuration
226 # dialog. IDLE promotes the preferred Python indentation: use spaces!
227 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
228 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000229
230 # tabwidth is the display width of a literal tab character.
231 # CAUTION: telling Tk to use anything other than its default
232 # tab setting causes it to use an entirely different tabbing algorithm,
233 # treating tab stops as fixed distances from the left margin.
234 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000235 self.tabwidth = 8 # must remain 8 until Tk is fixed.
236
237 # indentwidth is the number of screen characters per indent level.
238 # The recommended Python indentation is four spaces.
239 self.indentwidth = self.tabwidth
240 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000241
242 # If context_use_ps1 is true, parsing searches back for a ps1 line;
243 # else searches for a popular (if, def, ...) Python stmt.
244 self.context_use_ps1 = False
245
246 # When searching backwards for a reliable place to begin parsing,
247 # first start num_context_lines[0] lines back, then
248 # num_context_lines[1] lines back if that didn't work, and so on.
249 # The last value should be huge (larger than the # of lines in a
250 # conceivable file).
251 # Making the initial values larger slows things down more often.
252 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000253 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000254 self.undo = undo = self.UndoDelegator()
255 per.insertfilter(undo)
256 text.undo_block_start = undo.undo_block_start
257 text.undo_block_stop = undo.undo_block_stop
258 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000259 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000260 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000261 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000262 self.good_load = False
263 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000264 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000265 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000266 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000267 if io.loadfile(filename):
268 self.good_load = True
269 is_py_src = self.ispythonsource(filename)
270 self.set_indentation_params(is_py_src)
271 if is_py_src:
272 self.color = color = self.ColorDelegator()
273 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000274 else:
275 io.set_filename(filename)
Christian Heimesa156e092008-02-16 07:38:31 +0000276 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000277 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000278 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000279 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000280 menu = self.menudict.get('windows')
281 if menu:
282 end = menu.index("end")
283 if end is None:
284 end = -1
285 if end >= 0:
286 menu.add_separator()
287 end = end + 1
288 self.wmenu_end = end
289 WindowList.register_callback(self.postwindowsmenu)
290
291 # Some abstractions so IDLE extensions are cross-IDE
292 self.askyesno = tkMessageBox.askyesno
293 self.askinteger = tkSimpleDialog.askinteger
294 self.showerror = tkMessageBox.showerror
295
Martin v. Löwis307021f2005-11-27 16:59:04 +0000296 def _filename_to_unicode(self, filename):
297 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000298 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000299 return filename
300 else:
301 try:
302 return filename.decode(self.filesystemencoding)
303 except UnicodeDecodeError:
304 # XXX
305 try:
306 return filename.decode(self.encoding)
307 except UnicodeDecodeError:
308 # byte-to-byte conversion
309 return filename.decode('iso8859-1')
310
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000311 def new_callback(self, event):
312 dirname, basename = self.io.defaultfilename()
313 self.flist.new(dirname)
314 return "break"
315
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000316 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400317 if (event.state & 4) != 0 and event.keysym == "Home":
318 # state&4==Control. If <Control-Home>, use the Tk binding.
319 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000320 if self.text.index("iomark") and \
321 self.text.compare("iomark", "<=", "insert lineend") and \
322 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400323 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000324 insertpt = int(self.text.index("iomark").split(".")[1])
325 else:
326 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000327 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000328 if line[insertpt] not in (' ','\t'):
329 break
330 else:
331 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000332 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000333 if insertpt == lineat:
334 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000335 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000336 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400337 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000338 self.text.tag_remove("sel", "1.0", "end")
339 else:
340 if not self.text.index("sel.first"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400341 self.text.mark_set("my_anchor", "insert") # there was no previous selection
342 else:
343 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
344 self.text.mark_set("my_anchor", "sel.first") # extend back
345 else:
346 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000347 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400348 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000349 if self.text.compare(first,">",last):
350 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000351 self.text.tag_remove("sel", "1.0", "end")
352 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000353 self.text.mark_set("insert", dest)
354 self.text.see("insert")
355 return "break"
356
David Scherer7aced172000-08-15 01:13:23 +0000357 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000358 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000359 if macosxSupport.runningAsOSXApp():
360 # Insert some padding to avoid obscuring some of the statusbar
361 # by the resize widget.
362 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000363 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
364 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
365 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000366 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
367 self.text.event_add("<<set-line-and-column>>",
368 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000369 self.text.after_idle(self.set_line_and_column)
370
371 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000372 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000373 self.status_bar.set_label('column', 'Col: %s' % column)
374 self.status_bar.set_label('line', 'Ln: %s' % line)
375
David Scherer7aced172000-08-15 01:13:23 +0000376 menu_specs = [
377 ("file", "_File"),
378 ("edit", "_Edit"),
379 ("format", "F_ormat"),
380 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000381 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000382 ("windows", "_Windows"),
383 ("help", "_Help"),
384 ]
385
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000386 if macosxSupport.runningAsOSXApp():
387 del menu_specs[-3]
388 menu_specs[-2] = ("windows", "_Window")
389
390
David Scherer7aced172000-08-15 01:13:23 +0000391 def createmenubar(self):
392 mbar = self.menubar
393 self.menudict = menudict = {}
394 for name, label in self.menu_specs:
395 underline, label = prepstr(label)
396 menudict[name] = menu = Menu(mbar, name=name)
397 mbar.add_cascade(label=label, menu=menu, underline=underline)
Georg Brandlaedd2892010-12-19 10:10:32 +0000398 if macosxSupport.isCarbonAquaTk(self.root):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000399 # Insert the application menu
400 menudict['application'] = menu = Menu(mbar, name='apple')
401 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000402 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000403 self.recent_files_menu = Menu(self.menubar)
404 self.menudict['file'].insert_cascade(3, label='Recent Files',
405 underline=0,
406 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000407 self.base_helpmenu_length = self.menudict['help'].index(END)
408 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000409
410 def postwindowsmenu(self):
411 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000412 menu = self.menudict['windows']
413 end = menu.index("end")
414 if end is None:
415 end = -1
416 if end > self.wmenu_end:
417 menu.delete(self.wmenu_end+1, end)
418 WindowList.add_windows_to_menu(menu)
419
420 rmenu = None
421
422 def right_menu_event(self, event):
423 self.text.tag_remove("sel", "1.0", "end")
424 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
425 if not self.rmenu:
426 self.make_rmenu()
427 rmenu = self.rmenu
428 self.event = event
429 iswin = sys.platform[:3] == 'win'
430 if iswin:
431 self.text.config(cursor="arrow")
432 rmenu.tk_popup(event.x_root, event.y_root)
433 if iswin:
434 self.text.config(cursor="ibeam")
435
436 rmenu_specs = [
437 # ("Label", "<<virtual-event>>"), ...
438 ("Close", "<<close-window>>"), # Example
439 ]
440
441 def make_rmenu(self):
442 rmenu = Menu(self.text, tearoff=0)
443 for label, eventname in self.rmenu_specs:
444 def command(text=self.text, eventname=eventname):
445 text.event_generate(eventname)
446 rmenu.add_command(label=label, command=command)
447 self.rmenu = rmenu
448
449 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000450 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000451
Steven M. Gava3b55a892001-11-21 05:56:26 +0000452 def config_dialog(self, event=None):
453 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000454
David Scherer7aced172000-08-15 01:13:23 +0000455 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000456 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000457 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000458
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000459 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000460 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000461 try:
462 os.startfile(self.help_url)
463 except WindowsError as why:
464 tkMessageBox.showerror(title='Document Start Failure',
465 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000466 else:
467 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000468 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000469
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000470 def cut(self,event):
471 self.text.event_generate("<<Cut>>")
472 return "break"
473
474 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000475 if not self.text.tag_ranges("sel"):
476 # There is no selection, so do nothing and maybe interrupt.
477 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000478 self.text.event_generate("<<Copy>>")
479 return "break"
480
481 def paste(self,event):
482 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000483 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000484 return "break"
485
David Scherer7aced172000-08-15 01:13:23 +0000486 def select_all(self, event=None):
487 self.text.tag_add("sel", "1.0", "end-1c")
488 self.text.mark_set("insert", "1.0")
489 self.text.see("insert")
490 return "break"
491
492 def remove_selection(self, event=None):
493 self.text.tag_remove("sel", "1.0", "end")
494 self.text.see("insert")
495
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000496 def move_at_edge_if_selection(self, edge_index):
497 """Cursor move begins at start or end of selection
498
499 When a left/right cursor key is pressed create and return to Tkinter a
500 function which causes a cursor move from the associated edge of the
501 selection.
502
503 """
504 self_text_index = self.text.index
505 self_text_mark_set = self.text.mark_set
506 edges_table = ("sel.first+1c", "sel.last-1c")
507 def move_at_edge(event):
508 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
509 try:
510 self_text_index("sel.first")
511 self_text_mark_set("insert", edges_table[edge_index])
512 except TclError:
513 pass
514 return move_at_edge
515
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000516 def del_word_left(self, event):
517 self.text.event_generate('<Meta-Delete>')
518 return "break"
519
520 def del_word_right(self, event):
521 self.text.event_generate('<Meta-d>')
522 return "break"
523
Steven M. Gavac5976402002-01-04 03:06:08 +0000524 def find_event(self, event):
525 SearchDialog.find(self.text)
526 return "break"
527
528 def find_again_event(self, event):
529 SearchDialog.find_again(self.text)
530 return "break"
531
532 def find_selection_event(self, event):
533 SearchDialog.find_selection(self.text)
534 return "break"
535
536 def find_in_files_event(self, event):
537 GrepDialog.grep(self.text, self.io, self.flist)
538 return "break"
539
540 def replace_event(self, event):
541 ReplaceDialog.replace(self.text)
542 return "break"
543
544 def goto_line_event(self, event):
545 text = self.text
546 lineno = tkSimpleDialog.askinteger("Goto",
547 "Go to line number:",parent=text)
548 if lineno is None:
549 return "break"
550 if lineno <= 0:
551 text.bell()
552 return "break"
553 text.mark_set("insert", "%d.0" % lineno)
554 text.see("insert")
555
David Scherer7aced172000-08-15 01:13:23 +0000556 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000557 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000558 try:
559 name = self.text.get("sel.first", "sel.last")
560 except TclError:
561 name = ""
562 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000563 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000564 name = tkSimpleDialog.askstring("Module",
565 "Enter the name of a Python module\n"
566 "to search on sys.path and open:",
567 parent=self.text, initialvalue=name)
568 if name:
569 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000570 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000571 return
David Scherer7aced172000-08-15 01:13:23 +0000572 # XXX Ought to insert current file's directory in front of path
573 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000574 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000575 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000576 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
577 return
578 if type != imp.PY_SOURCE:
579 tkMessageBox.showerror("Unsupported type",
580 "%s is not a source module" % name, parent=self.text)
581 return
582 if f:
583 f.close()
584 if self.flist:
585 self.flist.open(file)
586 else:
587 self.io.loadfile(file)
588
589 def open_class_browser(self, event=None):
590 filename = self.io.filename
591 if not filename:
592 tkMessageBox.showerror(
593 "No filename",
594 "This buffer has no associated filename",
595 master=self.text)
596 self.text.focus_set()
597 return None
598 head, tail = os.path.split(filename)
599 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000600 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000601 ClassBrowser.ClassBrowser(self.flist, base, [head])
602
603 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000604 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000605 PathBrowser.PathBrowser(self.flist)
606
607 def gotoline(self, lineno):
608 if lineno is not None and lineno > 0:
609 self.text.mark_set("insert", "%d.0" % lineno)
610 self.text.tag_remove("sel", "1.0", "end")
611 self.text.tag_add("sel", "insert", "insert +1l")
612 self.center()
613
614 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000615 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000616 return True
David Scherer7aced172000-08-15 01:13:23 +0000617 base, ext = os.path.splitext(os.path.basename(filename))
618 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000619 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000620 line = self.text.get('1.0', '1.0 lineend')
621 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000622
623 def close_hook(self):
624 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000625 self.flist.unregister_maybe_terminate(self)
626 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000627
628 def set_close_hook(self, close_hook):
629 self.close_hook = close_hook
630
631 def filename_change_hook(self):
632 if self.flist:
633 self.flist.filename_changed_edit(self)
634 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000635 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000636 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000637
Christian Heimesa156e092008-02-16 07:38:31 +0000638 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000639 if self.color:
640 return
Christian Heimesa156e092008-02-16 07:38:31 +0000641 if self.ispythonsource(self.io.filename):
642 self.color = self.ColorDelegator()
643 # can add more colorizers here...
644 if self.color:
645 self.per.removefilter(self.undo)
646 self.per.insertfilter(self.color)
647 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000648
Christian Heimesa156e092008-02-16 07:38:31 +0000649 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000650 if not self.color:
651 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000652 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000653 self.per.removefilter(self.color)
654 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000655
Steven M. Gavab77d3432002-03-02 07:16:21 +0000656 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000657 "Update the colour theme"
658 # Called from self.filename_change_hook and from configDialog.py
659 self._rmcolorizer()
660 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000661 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000662 normal_colors = idleConf.GetHighlight(theme, 'normal')
663 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
664 select_colors = idleConf.GetHighlight(theme, 'hilite')
665 self.text.config(
666 foreground=normal_colors['foreground'],
667 background=normal_colors['background'],
668 insertbackground=cursor_color,
669 selectforeground=select_colors['foreground'],
670 selectbackground=select_colors['background'],
671 )
David Scherer7aced172000-08-15 01:13:23 +0000672
Guido van Rossum33d26892007-08-05 15:29:28 +0000673 IDENTCHARS = string.ascii_letters + string.digits + "_"
674
675 def colorize_syntax_error(self, text, pos):
676 text.tag_add("ERROR", pos)
677 char = text.get(pos)
678 if char and char in self.IDENTCHARS:
679 text.tag_add("ERROR", pos + " wordstart", pos)
680 if '\n' == text.get(pos): # error at line end
681 text.mark_set("insert", pos)
682 else:
683 text.mark_set("insert", pos + "+1c")
684 text.see(pos)
685
Steven M. Gavab1585412002-03-12 00:21:56 +0000686 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000687 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000688 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000689 fontWeight='normal'
690 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
691 fontWeight='bold'
692 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
693 idleConf.GetOption('main','EditorWindow','font-size'),
694 fontWeight))
695
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000696 def RemoveKeybindings(self):
697 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000698 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000699 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000700 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000701 self.text.event_delete(event, *keylist)
702 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000703 xkeydefs = idleConf.GetExtensionBindings(extensionName)
704 if xkeydefs:
705 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000706 self.text.event_delete(event, *keylist)
707
708 def ApplyKeybindings(self):
709 "Update the keybindings after they are changed"
710 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000711 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000712 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000713 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000714 xkeydefs = idleConf.GetExtensionBindings(extensionName)
715 if xkeydefs:
716 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000717 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000718 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000719 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000720 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000721 for item in menu[1]:
722 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000723 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000724 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000725 menu = self.menudict[menubarItem]
726 end = menu.index(END) + 1
727 for index in range(0, end):
728 if menu.type(index) == 'command':
729 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000730 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000731 itemName = menu.entrycget(index, 'label')
732 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000733 if menubarItem in menuEventDict:
734 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000735 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000736 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000737 accel = get_accelerator(keydefs, event)
738 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000739
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000740 def set_notabs_indentwidth(self):
741 "Update the indentwidth if changed and not using tabs in this window"
742 # Called from configDialog.py
743 if not self.usetabs:
744 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
745 type='int')
746
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000747 def reset_help_menu_entries(self):
748 "Update the additional help entries on the Help menu"
749 help_list = idleConf.GetAllExtraHelpSourcesList()
750 helpmenu = self.menudict['help']
751 # first delete the extra help entries, if any
752 helpmenu_length = helpmenu.index(END)
753 if helpmenu_length > self.base_helpmenu_length:
754 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
755 # then rebuild them
756 if help_list:
757 helpmenu.add_separator()
758 for entry in help_list:
759 cmd = self.__extra_help_callback(entry[1])
760 helpmenu.add_command(label=entry[0], command=cmd)
761 # and update the menu dictionary
762 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000763
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000764 def __extra_help_callback(self, helpfile):
765 "Create a callback with the helpfile value frozen at definition time"
766 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000767 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000768 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000769 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000770 try:
771 os.startfile(helpfile)
772 except WindowsError as why:
773 tkMessageBox.showerror(title='Document Start Failure',
774 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000775 else:
776 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000777 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000778
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000779 def update_recent_files_list(self, new_file=None):
780 "Load and update the recent files list and menus"
781 rf_list = []
782 if os.path.exists(self.recent_files_path):
Ned Deily122539e2011-01-24 21:46:44 +0000783 rf_list_file = open(self.recent_files_path,'r',
784 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000785 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000786 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000787 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000788 rf_list_file.close()
789 if new_file:
790 new_file = os.path.abspath(new_file) + '\n'
791 if new_file in rf_list:
792 rf_list.remove(new_file) # move to top
793 rf_list.insert(0, new_file)
794 # clean and save the recent files list
795 bad_paths = []
796 for path in rf_list:
797 if '\0' in path or not os.path.exists(path[0:-1]):
798 bad_paths.append(path)
799 rf_list = [path for path in rf_list if path not in bad_paths]
800 ulchars = "1234567890ABCDEFGHIJK"
801 rf_list = rf_list[0:len(ulchars)]
Ned Deily122539e2011-01-24 21:46:44 +0000802 rf_file = open(self.recent_files_path, 'w',
803 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000804 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000805 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000806 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000807 rf_file.close()
808 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000809 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000810 menu = instance.recent_files_menu
811 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000812 for i, file_name in enumerate(rf_list):
813 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000814 # make unicode string to display non-ASCII chars correctly
815 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000816 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000817 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000818 command=callback,
819 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000820
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000821 def __recent_file_callback(self, file_name):
822 def open_recent_file(fn_closure=file_name):
823 self.io.open(editFile=fn_closure)
824 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000825
David Scherer7aced172000-08-15 01:13:23 +0000826 def saved_change_hook(self):
827 short = self.short_title()
828 long = self.long_title()
829 if short and long:
830 title = short + " - " + long
831 elif short:
832 title = short
833 elif long:
834 title = long
835 else:
836 title = "Untitled"
837 icon = short or long or title
838 if not self.get_saved():
839 title = "*%s*" % title
840 icon = "*%s" % icon
841 self.top.wm_title(title)
842 self.top.wm_iconname(icon)
843
844 def get_saved(self):
845 return self.undo.get_saved()
846
847 def set_saved(self, flag):
848 self.undo.set_saved(flag)
849
850 def reset_undo(self):
851 self.undo.reset_undo()
852
853 def short_title(self):
854 filename = self.io.filename
855 if filename:
856 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000857 # return unicode string to display non-ASCII chars correctly
858 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000859
860 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000861 # return unicode string to display non-ASCII chars correctly
862 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000863
864 def center_insert_event(self, event):
865 self.center()
866
867 def center(self, mark="insert"):
868 text = self.text
869 top, bot = self.getwindowlines()
870 lineno = self.getlineno(mark)
871 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000872 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000873 text.yview(float(newtop))
874
875 def getwindowlines(self):
876 text = self.text
877 top = self.getlineno("@0,0")
878 bot = self.getlineno("@0,65535")
879 if top == bot and text.winfo_height() == 1:
880 # Geometry manager hasn't run yet
881 height = int(text['height'])
882 bot = top + height - 1
883 return top, bot
884
885 def getlineno(self, mark="insert"):
886 text = self.text
887 return int(float(text.index(mark)))
888
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000889 def get_geometry(self):
890 "Return (width, height, x, y)"
891 geom = self.top.wm_geometry()
892 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000893 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000894
David Scherer7aced172000-08-15 01:13:23 +0000895 def close_event(self, event):
896 self.close()
897
898 def maybesave(self):
899 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000900 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000901 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000902 self.top.deiconify()
903 self.top.lower()
904 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000905 return self.io.maybesave()
906
907 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000908 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000909 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000910 self._close()
911 return reply
912
913 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000914 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000915 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000916 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000917 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000918 self.io.close()
919 self.io = None
920 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000921 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000922 self.color.close(False)
923 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000924 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000925 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000926 self.per.close()
927 self.per = None
928 self.top.destroy()
929 if self.close_hook:
930 # unless override: unregister from flist, terminate if last window
931 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000932
933 def load_extensions(self):
934 self.extensions = {}
935 self.load_standard_extensions()
936
937 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000938 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000939 if hasattr(ins, "close"):
940 ins.close()
941 self.extensions = {}
942
943 def load_standard_extensions(self):
944 for name in self.get_standard_extension_names():
945 try:
946 self.load_extension(name)
947 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000948 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000949 traceback.print_exc()
950
951 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000952 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000953
954 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000955 try:
956 mod = __import__(name, globals(), locals(), [])
957 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000958 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000959 raise
David Scherer7aced172000-08-15 01:13:23 +0000960 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000961 keydefs = idleConf.GetExtensionBindings(name)
962 if hasattr(cls, "menudefs"):
963 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000964 ins = cls(self)
965 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000966 if keydefs:
967 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000968 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000969 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000970 while methodname[:1] == '<':
971 methodname = methodname[1:]
972 while methodname[-1:] == '>':
973 methodname = methodname[:-1]
974 methodname = methodname + "_event"
975 if hasattr(ins, methodname):
976 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000977
978 def apply_bindings(self, keydefs=None):
979 if keydefs is None:
980 keydefs = self.Bindings.default_keydefs
981 text = self.text
982 text.keydefs = keydefs
983 for event, keylist in keydefs.items():
984 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000985 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000986
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000987 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000988 """Add appropriate entries to the menus and submenus
989
990 Menus that are absent or None in self.menudict are ignored.
991 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000992 if menudefs is None:
993 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000994 if keydefs is None:
995 keydefs = self.Bindings.default_keydefs
996 menudict = self.menudict
997 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000998 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000999 menu = menudict.get(mname)
1000 if not menu:
1001 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001002 for entry in entrylist:
1003 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001004 menu.add_separator()
1005 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001006 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001007 checkbutton = (label[:1] == '!')
1008 if checkbutton:
1009 label = label[1:]
1010 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001011 accelerator = get_accelerator(keydefs, eventname)
1012 def command(text=text, eventname=eventname):
1013 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001014 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001015 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001016 menu.add_checkbutton(label=label, underline=underline,
1017 command=command, accelerator=accelerator,
1018 variable=var)
1019 else:
1020 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001021 command=command,
1022 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001023
1024 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001025 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001026 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001027 value = var.get()
1028 return value
1029 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001030 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001031
1032 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001033 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001034 if var:
1035 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001036 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001037 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001038
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001039 def get_var_obj(self, name, vartype=None):
1040 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001041 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001042 # create a Tkinter variable object with self.text as master:
1043 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001044 return var
1045
1046 # Tk implementations of "virtual text methods" -- each platform
1047 # reusing IDLE's support code needs to define these for its GUI's
1048 # flavor of widget.
1049
1050 # Is character at text_index in a Python string? Return 0 for
1051 # "guaranteed no", true for anything else. This info is expensive
1052 # to compute ab initio, but is probably already known by the
1053 # platform's colorizer.
1054
1055 def is_char_in_string(self, text_index):
1056 if self.color:
1057 # Return true iff colorizer hasn't (re)gotten this far
1058 # yet, or the character is tagged as being in a string
1059 return self.text.tag_prevrange("TODO", text_index) or \
1060 "STRING" in self.text.tag_names(text_index)
1061 else:
1062 # The colorizer is missing: assume the worst
1063 return 1
1064
1065 # If a selection is defined in the text widget, return (start,
1066 # end) as Tkinter text indices, otherwise return (None, None)
1067 def get_selection_indices(self):
1068 try:
1069 first = self.text.index("sel.first")
1070 last = self.text.index("sel.last")
1071 return first, last
1072 except TclError:
1073 return None, None
1074
1075 # Return the text widget's current view of what a tab stop means
1076 # (equivalent width in spaces).
1077
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001078 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001079 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1080 return int(current)
1081
1082 # Set the text widget's current view of what a tab stop means.
1083
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001084 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001085 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001086 if self.get_tk_tabwidth() != newtabwidth:
1087 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001088 pixels = text.tk.call("font", "measure", text["font"],
1089 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001090 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001091 text.configure(tabs=pixels)
1092
Guido van Rossum33d26892007-08-05 15:29:28 +00001093### begin autoindent code ### (configuration was moved to beginning of class)
1094
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001095 def set_indentation_params(self, is_py_src, guess=True):
1096 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001097 i = self.guess_indent()
1098 if 2 <= i <= 8:
1099 self.indentwidth = i
1100 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001101 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001102 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001103
1104 def smart_backspace_event(self, event):
1105 text = self.text
1106 first, last = self.get_selection_indices()
1107 if first and last:
1108 text.delete(first, last)
1109 text.mark_set("insert", first)
1110 return "break"
1111 # Delete whitespace left, until hitting a real char or closest
1112 # preceding virtual tab stop.
1113 chars = text.get("insert linestart", "insert")
1114 if chars == '':
1115 if text.compare("insert", ">", "1.0"):
1116 # easy: delete preceding newline
1117 text.delete("insert-1c")
1118 else:
1119 text.bell() # at start of buffer
1120 return "break"
1121 if chars[-1] not in " \t":
1122 # easy: delete preceding real char
1123 text.delete("insert-1c")
1124 return "break"
1125 # Ick. It may require *inserting* spaces if we back up over a
1126 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001127 tabwidth = self.tabwidth
1128 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001129 assert have > 0
1130 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001131 # Debug prompt is multilined....
1132 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001133 ncharsdeleted = 0
1134 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001135 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001136 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001137 chars = chars[:-1]
1138 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001139 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001140 if have <= want or chars[-1] not in " \t":
1141 break
1142 text.undo_block_start()
1143 text.delete("insert-%dc" % ncharsdeleted, "insert")
1144 if have < want:
1145 text.insert("insert", ' ' * (want - have))
1146 text.undo_block_stop()
1147 return "break"
1148
1149 def smart_indent_event(self, event):
1150 # if intraline selection:
1151 # delete it
1152 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001153 # do indent-region
1154 # else:
1155 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001156 text = self.text
1157 first, last = self.get_selection_indices()
1158 text.undo_block_start()
1159 try:
1160 if first and last:
1161 if index2line(first) != index2line(last):
1162 return self.indent_region_event(event)
1163 text.delete(first, last)
1164 text.mark_set("insert", first)
1165 prefix = text.get("insert linestart", "insert")
1166 raw, effective = classifyws(prefix, self.tabwidth)
1167 if raw == len(prefix):
1168 # only whitespace to the left
1169 self.reindent_to(effective + self.indentwidth)
1170 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001171 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001172 if self.usetabs:
1173 pad = '\t'
1174 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001175 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001176 n = self.indentwidth
1177 pad = ' ' * (n - effective % n)
1178 text.insert("insert", pad)
1179 text.see("insert")
1180 return "break"
1181 finally:
1182 text.undo_block_stop()
1183
1184 def newline_and_indent_event(self, event):
1185 text = self.text
1186 first, last = self.get_selection_indices()
1187 text.undo_block_start()
1188 try:
1189 if first and last:
1190 text.delete(first, last)
1191 text.mark_set("insert", first)
1192 line = text.get("insert linestart", "insert")
1193 i, n = 0, len(line)
1194 while i < n and line[i] in " \t":
1195 i = i+1
1196 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001197 # the cursor is in or at leading indentation in a continuation
1198 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001199 text.insert("insert linestart", '\n')
1200 return "break"
1201 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001202 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001203 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001204 last_line_of_prompt = sys.ps1.split('\n')[-1]
1205 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001206 line = line[:-1]
1207 i = i+1
1208 if i:
1209 text.delete("insert - %d chars" % i, "insert")
1210 # strip whitespace after insert point
1211 while text.get("insert") in " \t":
1212 text.delete("insert")
1213 # start new line
1214 text.insert("insert", '\n')
1215
1216 # adjust indentation for continuations and block
1217 # open/close first need to find the last stmt
1218 lno = index2line(text.index('insert'))
1219 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001220 if not self.context_use_ps1:
1221 for context in self.num_context_lines:
1222 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001223 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001224 rawtext = text.get(startatindex, "insert")
1225 y.set_str(rawtext)
1226 bod = y.find_good_parse_start(
1227 self.context_use_ps1,
1228 self._build_char_in_string_func(startatindex))
1229 if bod is not None or startat == 1:
1230 break
1231 y.set_lo(bod or 0)
1232 else:
1233 r = text.tag_prevrange("console", "insert")
1234 if r:
1235 startatindex = r[1]
1236 else:
1237 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001238 rawtext = text.get(startatindex, "insert")
1239 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001240 y.set_lo(0)
1241
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001242 c = y.get_continuation_type()
1243 if c != PyParse.C_NONE:
1244 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001245 if c == PyParse.C_STRING_FIRST_LINE:
1246 # after the first line of a string; do not indent at all
1247 pass
1248 elif c == PyParse.C_STRING_NEXT_LINES:
1249 # inside a string which started before this line;
1250 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001251 text.insert("insert", indent)
1252 elif c == PyParse.C_BRACKET:
1253 # line up with the first (if any) element of the
1254 # last open bracket structure; else indent one
1255 # level beyond the indent of the line with the
1256 # last open bracket
1257 self.reindent_to(y.compute_bracket_indent())
1258 elif c == PyParse.C_BACKSLASH:
1259 # if more than one line in this stmt already, just
1260 # mimic the current indent; else if initial line
1261 # has a start on an assignment stmt, indent to
1262 # beyond leftmost =; else to beyond first chunk of
1263 # non-whitespace on initial line
1264 if y.get_num_lines_in_stmt() > 1:
1265 text.insert("insert", indent)
1266 else:
1267 self.reindent_to(y.compute_backslash_indent())
1268 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001269 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001270 return "break"
1271
1272 # This line starts a brand new stmt; indent relative to
1273 # indentation of initial line of closest preceding
1274 # interesting stmt.
1275 indent = y.get_base_indent_string()
1276 text.insert("insert", indent)
1277 if y.is_block_opener():
1278 self.smart_indent_event(event)
1279 elif indent and y.is_block_closer():
1280 self.smart_backspace_event(event)
1281 return "break"
1282 finally:
1283 text.see("insert")
1284 text.undo_block_stop()
1285
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001286 # Our editwin provides a is_char_in_string function that works
1287 # with a Tk text index, but PyParse only knows about offsets into
1288 # a string. This builds a function for PyParse that accepts an
1289 # offset.
1290
1291 def _build_char_in_string_func(self, startindex):
1292 def inner(offset, _startindex=startindex,
1293 _icis=self.is_char_in_string):
1294 return _icis(_startindex + "+%dc" % offset)
1295 return inner
1296
1297 def indent_region_event(self, event):
1298 head, tail, chars, lines = self.get_region()
1299 for pos in range(len(lines)):
1300 line = lines[pos]
1301 if line:
1302 raw, effective = classifyws(line, self.tabwidth)
1303 effective = effective + self.indentwidth
1304 lines[pos] = self._make_blanks(effective) + line[raw:]
1305 self.set_region(head, tail, chars, lines)
1306 return "break"
1307
1308 def dedent_region_event(self, event):
1309 head, tail, chars, lines = self.get_region()
1310 for pos in range(len(lines)):
1311 line = lines[pos]
1312 if line:
1313 raw, effective = classifyws(line, self.tabwidth)
1314 effective = max(effective - self.indentwidth, 0)
1315 lines[pos] = self._make_blanks(effective) + line[raw:]
1316 self.set_region(head, tail, chars, lines)
1317 return "break"
1318
1319 def comment_region_event(self, event):
1320 head, tail, chars, lines = self.get_region()
1321 for pos in range(len(lines) - 1):
1322 line = lines[pos]
1323 lines[pos] = '##' + line
1324 self.set_region(head, tail, chars, lines)
1325
1326 def uncomment_region_event(self, event):
1327 head, tail, chars, lines = self.get_region()
1328 for pos in range(len(lines)):
1329 line = lines[pos]
1330 if not line:
1331 continue
1332 if line[:2] == '##':
1333 line = line[2:]
1334 elif line[:1] == '#':
1335 line = line[1:]
1336 lines[pos] = line
1337 self.set_region(head, tail, chars, lines)
1338
1339 def tabify_region_event(self, event):
1340 head, tail, chars, lines = self.get_region()
1341 tabwidth = self._asktabwidth()
1342 for pos in range(len(lines)):
1343 line = lines[pos]
1344 if line:
1345 raw, effective = classifyws(line, tabwidth)
1346 ntabs, nspaces = divmod(effective, tabwidth)
1347 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1348 self.set_region(head, tail, chars, lines)
1349
1350 def untabify_region_event(self, event):
1351 head, tail, chars, lines = self.get_region()
1352 tabwidth = self._asktabwidth()
1353 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001354 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001355 self.set_region(head, tail, chars, lines)
1356
1357 def toggle_tabs_event(self, event):
1358 if self.askyesno(
1359 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001360 "Turn tabs " + ("on", "off")[self.usetabs] +
1361 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001362 ("will be", "remains at")[self.usetabs] + " 8." +
1363 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001364 parent=self.text):
1365 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001366 # Try to prevent inconsistent indentation.
1367 # User must change indent width manually after using tabs.
1368 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001369 return "break"
1370
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001371 # XXX this isn't bound to anything -- see tabwidth comments
1372## def change_tabwidth_event(self, event):
1373## new = self._asktabwidth()
1374## if new != self.tabwidth:
1375## self.tabwidth = new
1376## self.set_indentation_params(0, guess=0)
1377## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001378
1379 def change_indentwidth_event(self, event):
1380 new = self.askinteger(
1381 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001382 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001383 parent=self.text,
1384 initialvalue=self.indentwidth,
1385 minvalue=2,
1386 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001387 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001388 self.indentwidth = new
1389 return "break"
1390
1391 def get_region(self):
1392 text = self.text
1393 first, last = self.get_selection_indices()
1394 if first and last:
1395 head = text.index(first + " linestart")
1396 tail = text.index(last + "-1c lineend +1c")
1397 else:
1398 head = text.index("insert linestart")
1399 tail = text.index("insert lineend +1c")
1400 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001401 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001402 return head, tail, chars, lines
1403
1404 def set_region(self, head, tail, chars, lines):
1405 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001406 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001407 if newchars == chars:
1408 text.bell()
1409 return
1410 text.tag_remove("sel", "1.0", "end")
1411 text.mark_set("insert", head)
1412 text.undo_block_start()
1413 text.delete(head, tail)
1414 text.insert(head, newchars)
1415 text.undo_block_stop()
1416 text.tag_add("sel", head, "insert")
1417
1418 # Make string that displays as n leading blanks.
1419
1420 def _make_blanks(self, n):
1421 if self.usetabs:
1422 ntabs, nspaces = divmod(n, self.tabwidth)
1423 return '\t' * ntabs + ' ' * nspaces
1424 else:
1425 return ' ' * n
1426
1427 # Delete from beginning of line to insert point, then reinsert
1428 # column logical (meaning use tabs if appropriate) spaces.
1429
1430 def reindent_to(self, column):
1431 text = self.text
1432 text.undo_block_start()
1433 if text.compare("insert linestart", "!=", "insert"):
1434 text.delete("insert linestart", "insert")
1435 if column:
1436 text.insert("insert", self._make_blanks(column))
1437 text.undo_block_stop()
1438
1439 def _asktabwidth(self):
1440 return self.askinteger(
1441 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001442 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001443 parent=self.text,
1444 initialvalue=self.indentwidth,
1445 minvalue=2,
1446 maxvalue=16) or self.tabwidth
1447
1448 # Guess indentwidth from text content.
1449 # Return guessed indentwidth. This should not be believed unless
1450 # it's in a reasonable range (e.g., it will be 0 if no indented
1451 # blocks are found).
1452
1453 def guess_indent(self):
1454 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1455 if opener and indented:
1456 raw, indentsmall = classifyws(opener, self.tabwidth)
1457 raw, indentlarge = classifyws(indented, self.tabwidth)
1458 else:
1459 indentsmall = indentlarge = 0
1460 return indentlarge - indentsmall
1461
1462# "line.col" -> line, as an int
1463def index2line(index):
1464 return int(float(index))
1465
1466# Look at the leading whitespace in s.
1467# Return pair (# of leading ws characters,
1468# effective # of leading blanks after expanding
1469# tabs to width tabwidth)
1470
1471def classifyws(s, tabwidth):
1472 raw = effective = 0
1473 for ch in s:
1474 if ch == ' ':
1475 raw = raw + 1
1476 effective = effective + 1
1477 elif ch == '\t':
1478 raw = raw + 1
1479 effective = (effective // tabwidth + 1) * tabwidth
1480 else:
1481 break
1482 return raw, effective
1483
1484import tokenize
1485_tokenize = tokenize
1486del tokenize
1487
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001488class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001489
1490 # .run() chews over the Text widget, looking for a block opener
1491 # and the stmt following it. Returns a pair,
1492 # (line containing block opener, line containing stmt)
1493 # Either or both may be None.
1494
1495 def __init__(self, text, tabwidth):
1496 self.text = text
1497 self.tabwidth = tabwidth
1498 self.i = self.finished = 0
1499 self.blkopenline = self.indentedline = None
1500
1501 def readline(self):
1502 if self.finished:
1503 return ""
1504 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001505 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001506 if self.text.compare(mark, ">=", "end"):
1507 return ""
1508 return self.text.get(mark, mark + " lineend+1c")
1509
1510 def tokeneater(self, type, token, start, end, line,
1511 INDENT=_tokenize.INDENT,
1512 NAME=_tokenize.NAME,
1513 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1514 if self.finished:
1515 pass
1516 elif type == NAME and token in OPENERS:
1517 self.blkopenline = line
1518 elif type == INDENT and self.blkopenline:
1519 self.indentedline = line
1520 self.finished = 1
1521
1522 def run(self):
1523 save_tabsize = _tokenize.tabsize
1524 _tokenize.tabsize = self.tabwidth
1525 try:
1526 try:
Trent Nelson428de652008-03-18 22:41:35 +00001527 tokens = _tokenize.generate_tokens(self.readline)
1528 for token in tokens:
1529 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001530 except _tokenize.TokenError:
1531 # since we cut off the tokenizer early, we can trigger
1532 # spurious errors
1533 pass
1534 finally:
1535 _tokenize.tabsize = save_tabsize
1536 return self.blkopenline, self.indentedline
1537
1538### end autoindent code ###
1539
David Scherer7aced172000-08-15 01:13:23 +00001540def prepstr(s):
1541 # Helper to extract the underscore from a string, e.g.
1542 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001543 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001544 if i >= 0:
1545 s = s[:i] + s[i+1:]
1546 return i, s
1547
1548
1549keynames = {
1550 'bracketleft': '[',
1551 'bracketright': ']',
1552 'slash': '/',
1553}
1554
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001555def get_accelerator(keydefs, eventname):
1556 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001557 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1558 # if not keylist:
1559 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1560 "<<open-module>>",
1561 "<<goto-line>>",
1562 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001563 return ""
1564 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001565 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001566 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1567 s = re.sub("Key-", "", s)
1568 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1569 s = re.sub("Control-", "Ctrl-", s)
1570 s = re.sub("-", "+", s)
1571 s = re.sub("><", " ", s)
1572 s = re.sub("<", "", s)
1573 s = re.sub(">", "", s)
1574 return s
1575
1576
1577def fixwordbreaks(root):
1578 # Make sure that Tk's double-click and next/previous word
1579 # operations use our definition of a word (i.e. an identifier)
1580 tk = root.tk
1581 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1582 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1583 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1584
1585
1586def test():
1587 root = Tk()
1588 fixwordbreaks(root)
1589 root.withdraw()
1590 if sys.argv[1:]:
1591 filename = sys.argv[1]
1592 else:
1593 filename = None
1594 edit = EditorWindow(root=root, filename=filename)
1595 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001596 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001597 root.mainloop()
1598 root.destroy()
1599
1600if __name__ == '__main__':
1601 test()