blob: 546fa9d026c0ad31e9cb3c6d79219512b4241378 [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
5from Tkinter import *
6import tkSimpleDialog
7import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00008
9import webbrowser
David Scherer7aced172000-08-15 01:13:23 +000010import idlever
11import WindowList
Steven M. Gavac5976402002-01-04 03:06:08 +000012import SearchDialog
13import GrepDialog
14import ReplaceDialog
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +000015import PyParse
Steven M. Gavadc72f482002-01-03 11:51:07 +000016from configHandler import idleConf
Steven M. Gava3b55a892001-11-21 05:56:26 +000017import aboutDialog, textView, configDialog
David Scherer7aced172000-08-15 01:13:23 +000018
19# The default tab setting for a Text widget, in average-width characters.
20TK_TABWIDTH_DEFAULT = 8
21
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000022def _find_module(fullname, path=None):
23 """Version of imp.find_module() that handles hierarchical module names"""
24
25 file = None
26 for tgt in fullname.split('.'):
27 if file is not None:
28 file.close() # close intermediate files
29 (file, filename, descr) = imp.find_module(tgt, path)
30 if descr[2] == imp.PY_SOURCE:
31 break # find but not load the source file
32 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000033 try:
34 path = module.__path__
35 except AttributeError:
36 raise ImportError, 'No source for module ' + module.__name__
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000037 return file, filename, descr
38
David Scherer7aced172000-08-15 01:13:23 +000039class EditorWindow:
David Scherer7aced172000-08-15 01:13:23 +000040 from Percolator import Percolator
41 from ColorDelegator import ColorDelegator
42 from UndoDelegator import UndoDelegator
43 from IOBinding import IOBinding
44 import Bindings
45 from Tkinter import Toplevel
46 from MultiStatusBar import MultiStatusBar
47
David Scherer7aced172000-08-15 01:13:23 +000048 vars = {}
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000049 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000050
51 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000052 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000053 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000054 if sys.platform.count('linux'):
55 # look for html docs in a couple of standard places
56 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
57 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
58 dochome = '/var/www/html/python/index.html'
59 else:
60 basepath = '/usr/share/doc/' # standard location
61 dochome = os.path.join(basepath, pyver,
62 'Doc', 'index.html')
Thomas Heller84ef1532003-09-23 20:53:10 +000063 elif sys.platform.count('win') or sys.platform.count('nt'):
64 chmfile = os.path.join(sys.prefix, "Python%d%d.chm" % sys.version_info[:2])
65 if os.path.isfile(chmfile):
66 dochome = chmfile
67 print "dochome =", dochome
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000068 dochome = os.path.normpath(dochome)
69 if os.path.isfile(dochome):
70 EditorWindow.help_url = dochome
71 else:
72 EditorWindow.help_url = "http://www.python.org/doc/current"
Steven M. Gavadc72f482002-01-03 11:51:07 +000073 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000074 self.flist = flist
75 root = root or flist.root
76 self.root = root
David Scherer7aced172000-08-15 01:13:23 +000077 self.menubar = Menu(root)
78 self.top = top = self.Toplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000079 if flist:
80 self.vars = flist.vars
81 #self.top.instanceDict makes flist.inversedict avalable to
82 #configDialog.py so it can access all EditorWindow instaces
83 self.top.instanceDict=flist.inversedict
Steven M. Gava1d46e402002-03-27 08:40:46 +000084 self.recentFilesPath=os.path.join(idleConf.GetUserCfgDir(),
85 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +000086 self.vbar = vbar = Scrollbar(top, name='vbar')
87 self.text_frame = text_frame = Frame(top)
Kurt B. Kaiser1061e722003-01-04 01:43:53 +000088 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaisera1dee062002-10-04 21:33:57 +000089 self.text = text = Text(text_frame, name='text', padx=5, wrap='none',
Steven M. Gavadc72f482002-01-03 11:51:07 +000090 foreground=idleConf.GetHighlight(currentTheme,
91 'normal',fgBg='fg'),
92 background=idleConf.GetHighlight(currentTheme,
93 'normal',fgBg='bg'),
94 highlightcolor=idleConf.GetHighlight(currentTheme,
95 'hilite',fgBg='fg'),
96 highlightbackground=idleConf.GetHighlight(currentTheme,
97 'hilite',fgBg='bg'),
98 insertbackground=idleConf.GetHighlight(currentTheme,
99 'cursor',fgBg='fg'),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000100 width=self.width,
Steven M. Gavadc72f482002-01-03 11:51:07 +0000101 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +0000102
103 self.createmenubar()
104 self.apply_bindings()
105
106 self.top.protocol("WM_DELETE_WINDOW", self.close)
107 self.top.bind("<<close-window>>", self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000108 text.bind("<<cut>>", self.cut)
109 text.bind("<<copy>>", self.copy)
110 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000111 text.bind("<<center-insert>>", self.center_insert_event)
112 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000113 text.bind("<<python-docs>>", self.python_docs)
114 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000115 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000116 text.bind("<<open-module>>", self.open_module)
117 text.bind("<<do-nothing>>", lambda event: "break")
118 text.bind("<<select-all>>", self.select_all)
119 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000120 text.bind("<<find>>", self.find_event)
121 text.bind("<<find-again>>", self.find_again_event)
122 text.bind("<<find-in-files>>", self.find_in_files_event)
123 text.bind("<<find-selection>>", self.find_selection_event)
124 text.bind("<<replace>>", self.replace_event)
125 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000126 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000127 text.bind("<<smart-backspace>>",self.smart_backspace_event)
128 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
129 text.bind("<<smart-indent>>",self.smart_indent_event)
130 text.bind("<<indent-region>>",self.indent_region_event)
131 text.bind("<<dedent-region>>",self.dedent_region_event)
132 text.bind("<<comment-region>>",self.comment_region_event)
133 text.bind("<<uncomment-region>>",self.uncomment_region_event)
134 text.bind("<<tabify-region>>",self.tabify_region_event)
135 text.bind("<<untabify-region>>",self.untabify_region_event)
136 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
137 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000138 text.bind("<Left>", self.move_at_edge_if_selection(0))
139 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000140
David Scherer7aced172000-08-15 01:13:23 +0000141 if flist:
142 flist.inversedict[self] = key
143 if key:
144 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000145 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000146 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
147 text.bind("<<open-class-browser>>", self.open_class_browser)
148 text.bind("<<open-path-browser>>", self.open_path_browser)
149
Steven M. Gava898a3652001-10-07 11:10:44 +0000150 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000151 vbar['command'] = text.yview
152 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000153 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000154 fontWeight='normal'
155 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
156 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000157 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000158 idleConf.GetOption('main','EditorWindow','font-size'),
159 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000160 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
161 text.pack(side=TOP, fill=BOTH, expand=1)
162 text.focus_set()
163
164 self.per = per = self.Percolator(text)
165 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000166 self.color = color = self.ColorDelegator()
167 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000168 else:
David Scherer7aced172000-08-15 01:13:23 +0000169 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000170
171 self.undo = undo = self.UndoDelegator()
172 per.insertfilter(undo)
173 text.undo_block_start = undo.undo_block_start
174 text.undo_block_stop = undo.undo_block_stop
175 undo.set_saved_change_hook(self.saved_change_hook)
176
177 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000178 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000179 io.set_filename_change_hook(self.filename_change_hook)
180
Steven M. Gava1d46e402002-03-27 08:40:46 +0000181 #create the Recent Files submenu
182 self.menuRecentFiles=Menu(self.menubar)
183 self.menudict['file'].insert_cascade(3,label='Recent Files',
184 underline=0,menu=self.menuRecentFiles)
185 self.UpdateRecentFilesList()
David Scherer7aced172000-08-15 01:13:23 +0000186
David Scherer7aced172000-08-15 01:13:23 +0000187 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000188 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000189 io.loadfile(filename)
190 else:
191 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000192 self.saved_change_hook()
193
194 self.load_extensions()
195
196 menu = self.menudict.get('windows')
197 if menu:
198 end = menu.index("end")
199 if end is None:
200 end = -1
201 if end >= 0:
202 menu.add_separator()
203 end = end + 1
204 self.wmenu_end = end
205 WindowList.register_callback(self.postwindowsmenu)
206
207 # Some abstractions so IDLE extensions are cross-IDE
208 self.askyesno = tkMessageBox.askyesno
209 self.askinteger = tkSimpleDialog.askinteger
210 self.showerror = tkMessageBox.showerror
211
212 if self.extensions.has_key('AutoIndent'):
213 self.extensions['AutoIndent'].set_indentation_params(
214 self.ispythonsource(filename))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000215
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000216 def new_callback(self, event):
217 dirname, basename = self.io.defaultfilename()
218 self.flist.new(dirname)
219 return "break"
220
David Scherer7aced172000-08-15 01:13:23 +0000221 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000222 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000223 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
224 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
225 self.status_bar.pack(side=BOTTOM, fill=X)
226 self.text.bind('<KeyRelease>', self.set_line_and_column)
227 self.text.bind('<ButtonRelease>', self.set_line_and_column)
228 self.text.after_idle(self.set_line_and_column)
229
230 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000231 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000232 self.status_bar.set_label('column', 'Col: %s' % column)
233 self.status_bar.set_label('line', 'Ln: %s' % line)
234
235 def wakeup(self):
236 if self.top.wm_state() == "iconic":
237 self.top.wm_deiconify()
238 else:
239 self.top.tkraise()
240 self.text.focus_set()
241
242 menu_specs = [
243 ("file", "_File"),
244 ("edit", "_Edit"),
245 ("format", "F_ormat"),
246 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000247 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000248 ("windows", "_Windows"),
249 ("help", "_Help"),
250 ]
251
252 def createmenubar(self):
253 mbar = self.menubar
254 self.menudict = menudict = {}
255 for name, label in self.menu_specs:
256 underline, label = prepstr(label)
257 menudict[name] = menu = Menu(mbar, name=name)
258 mbar.add_cascade(label=label, menu=menu, underline=underline)
259 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000260 self.base_helpmenu_length = self.menudict['help'].index(END)
261 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000262
263 def postwindowsmenu(self):
264 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000265 menu = self.menudict['windows']
266 end = menu.index("end")
267 if end is None:
268 end = -1
269 if end > self.wmenu_end:
270 menu.delete(self.wmenu_end+1, end)
271 WindowList.add_windows_to_menu(menu)
272
273 rmenu = None
274
275 def right_menu_event(self, event):
276 self.text.tag_remove("sel", "1.0", "end")
277 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
278 if not self.rmenu:
279 self.make_rmenu()
280 rmenu = self.rmenu
281 self.event = event
282 iswin = sys.platform[:3] == 'win'
283 if iswin:
284 self.text.config(cursor="arrow")
285 rmenu.tk_popup(event.x_root, event.y_root)
286 if iswin:
287 self.text.config(cursor="ibeam")
288
289 rmenu_specs = [
290 # ("Label", "<<virtual-event>>"), ...
291 ("Close", "<<close-window>>"), # Example
292 ]
293
294 def make_rmenu(self):
295 rmenu = Menu(self.text, tearoff=0)
296 for label, eventname in self.rmenu_specs:
297 def command(text=self.text, eventname=eventname):
298 text.event_generate(eventname)
299 rmenu.add_command(label=label, command=command)
300 self.rmenu = rmenu
301
302 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000303 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000304
Steven M. Gava3b55a892001-11-21 05:56:26 +0000305 def config_dialog(self, event=None):
306 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000307
David Scherer7aced172000-08-15 01:13:23 +0000308 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000309 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000310 textView.TextViewer(self.top,'Help',fn)
311
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000312 def python_docs(self, event=None):
313 if sys.platform.count('win') or sys.platform.count('nt'):
Steven M. Gava931625d2002-04-22 00:38:26 +0000314 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000315 return "break"
316 else:
317 webbrowser.open(self.help_url)
318 return "break"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000319
320 def display_docs(self, url):
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000321 if not (url.startswith('www') or url.startswith('http')):
322 url = os.path.normpath(url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000323 if sys.platform.count('win') or sys.platform.count('nt'):
324 os.startfile(url)
325 else:
326 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000327
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000328 def cut(self,event):
329 self.text.event_generate("<<Cut>>")
330 return "break"
331
332 def copy(self,event):
333 self.text.event_generate("<<Copy>>")
334 return "break"
335
336 def paste(self,event):
337 self.text.event_generate("<<Paste>>")
338 return "break"
339
David Scherer7aced172000-08-15 01:13:23 +0000340 def select_all(self, event=None):
341 self.text.tag_add("sel", "1.0", "end-1c")
342 self.text.mark_set("insert", "1.0")
343 self.text.see("insert")
344 return "break"
345
346 def remove_selection(self, event=None):
347 self.text.tag_remove("sel", "1.0", "end")
348 self.text.see("insert")
349
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000350 def move_at_edge_if_selection(self, edge_index):
351 """Cursor move begins at start or end of selection
352
353 When a left/right cursor key is pressed create and return to Tkinter a
354 function which causes a cursor move from the associated edge of the
355 selection.
356
357 """
358 self_text_index = self.text.index
359 self_text_mark_set = self.text.mark_set
360 edges_table = ("sel.first+1c", "sel.last-1c")
361 def move_at_edge(event):
362 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
363 try:
364 self_text_index("sel.first")
365 self_text_mark_set("insert", edges_table[edge_index])
366 except TclError:
367 pass
368 return move_at_edge
369
Steven M. Gavac5976402002-01-04 03:06:08 +0000370 def find_event(self, event):
371 SearchDialog.find(self.text)
372 return "break"
373
374 def find_again_event(self, event):
375 SearchDialog.find_again(self.text)
376 return "break"
377
378 def find_selection_event(self, event):
379 SearchDialog.find_selection(self.text)
380 return "break"
381
382 def find_in_files_event(self, event):
383 GrepDialog.grep(self.text, self.io, self.flist)
384 return "break"
385
386 def replace_event(self, event):
387 ReplaceDialog.replace(self.text)
388 return "break"
389
390 def goto_line_event(self, event):
391 text = self.text
392 lineno = tkSimpleDialog.askinteger("Goto",
393 "Go to line number:",parent=text)
394 if lineno is None:
395 return "break"
396 if lineno <= 0:
397 text.bell()
398 return "break"
399 text.mark_set("insert", "%d.0" % lineno)
400 text.see("insert")
401
David Scherer7aced172000-08-15 01:13:23 +0000402 def open_module(self, event=None):
403 # XXX Shouldn't this be in IOBinding or in FileList?
404 try:
405 name = self.text.get("sel.first", "sel.last")
406 except TclError:
407 name = ""
408 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000409 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000410 name = tkSimpleDialog.askstring("Module",
411 "Enter the name of a Python module\n"
412 "to search on sys.path and open:",
413 parent=self.text, initialvalue=name)
414 if name:
415 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000416 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000417 return
David Scherer7aced172000-08-15 01:13:23 +0000418 # XXX Ought to insert current file's directory in front of path
419 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000420 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000421 except (NameError, ImportError), msg:
422 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
423 return
424 if type != imp.PY_SOURCE:
425 tkMessageBox.showerror("Unsupported type",
426 "%s is not a source module" % name, parent=self.text)
427 return
428 if f:
429 f.close()
430 if self.flist:
431 self.flist.open(file)
432 else:
433 self.io.loadfile(file)
434
435 def open_class_browser(self, event=None):
436 filename = self.io.filename
437 if not filename:
438 tkMessageBox.showerror(
439 "No filename",
440 "This buffer has no associated filename",
441 master=self.text)
442 self.text.focus_set()
443 return None
444 head, tail = os.path.split(filename)
445 base, ext = os.path.splitext(tail)
446 import ClassBrowser
447 ClassBrowser.ClassBrowser(self.flist, base, [head])
448
449 def open_path_browser(self, event=None):
450 import PathBrowser
451 PathBrowser.PathBrowser(self.flist)
452
453 def gotoline(self, lineno):
454 if lineno is not None and lineno > 0:
455 self.text.mark_set("insert", "%d.0" % lineno)
456 self.text.tag_remove("sel", "1.0", "end")
457 self.text.tag_add("sel", "insert", "insert +1l")
458 self.center()
459
460 def ispythonsource(self, filename):
461 if not filename:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000462 return True
David Scherer7aced172000-08-15 01:13:23 +0000463 base, ext = os.path.splitext(os.path.basename(filename))
464 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000465 return True
David Scherer7aced172000-08-15 01:13:23 +0000466 try:
467 f = open(filename)
468 line = f.readline()
469 f.close()
470 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000471 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000472 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000473
474 def close_hook(self):
475 if self.flist:
476 self.flist.close_edit(self)
477
478 def set_close_hook(self, close_hook):
479 self.close_hook = close_hook
480
481 def filename_change_hook(self):
482 if self.flist:
483 self.flist.filename_changed_edit(self)
484 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000485 self.top.update_windowlist_registry(self)
David Scherer7aced172000-08-15 01:13:23 +0000486 if self.ispythonsource(self.io.filename):
487 self.addcolorizer()
488 else:
489 self.rmcolorizer()
490
491 def addcolorizer(self):
492 if self.color:
493 return
David Scherer7aced172000-08-15 01:13:23 +0000494 self.per.removefilter(self.undo)
495 self.color = self.ColorDelegator()
496 self.per.insertfilter(self.color)
497 self.per.insertfilter(self.undo)
498
499 def rmcolorizer(self):
500 if not self.color:
501 return
David Scherer7aced172000-08-15 01:13:23 +0000502 self.per.removefilter(self.undo)
503 self.per.removefilter(self.color)
504 self.color = None
505 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000506
Steven M. Gavab77d3432002-03-02 07:16:21 +0000507 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000508 "Update the colour theme if it is changed"
509 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000510 if self.color:
511 self.color = self.ColorDelegator()
512 self.per.insertfilter(self.color)
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000513 theme = idleConf.GetOption('main','Theme','name')
514 self.text.config(idleConf.GetHighlight(theme, "normal"))
David Scherer7aced172000-08-15 01:13:23 +0000515
Steven M. Gavab1585412002-03-12 00:21:56 +0000516 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000517 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000518 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000519 fontWeight='normal'
520 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
521 fontWeight='bold'
522 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
523 idleConf.GetOption('main','EditorWindow','font-size'),
524 fontWeight))
525
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000526 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000527 "Update the keybindings if they are changed"
528 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000529 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
530 keydefs = self.Bindings.default_keydefs
531 for event, keylist in keydefs.items():
532 self.text.event_delete(event)
533 self.apply_bindings()
534 #update menu accelerators
535 menuEventDict={}
536 for menu in self.Bindings.menudefs:
537 menuEventDict[menu[0]]={}
538 for item in menu[1]:
539 if item:
540 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
541 for menubarItem in self.menudict.keys():
542 menu=self.menudict[menubarItem]
543 end=menu.index(END)+1
544 for index in range(0,end):
545 if menu.type(index)=='command':
546 accel=menu.entrycget(index,'accelerator')
547 if accel:
548 itemName=menu.entrycget(index,'label')
549 event=''
550 if menuEventDict.has_key(menubarItem):
551 if menuEventDict[menubarItem].has_key(itemName):
552 event=menuEventDict[menubarItem][itemName]
553 if event:
554 #print 'accel was:',accel
555 accel=get_accelerator(keydefs, event)
556 menu.entryconfig(index,accelerator=accel)
557 #print 'accel now:',accel,'\n'
558
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000559 def reset_help_menu_entries(self):
560 "Update the additional help entries on the Help menu"
561 help_list = idleConf.GetAllExtraHelpSourcesList()
562 helpmenu = self.menudict['help']
563 # first delete the extra help entries, if any
564 helpmenu_length = helpmenu.index(END)
565 if helpmenu_length > self.base_helpmenu_length:
566 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
567 # then rebuild them
568 if help_list:
569 helpmenu.add_separator()
570 for entry in help_list:
571 cmd = self.__extra_help_callback(entry[1])
572 helpmenu.add_command(label=entry[0], command=cmd)
573 # and update the menu dictionary
574 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000575
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000576 def __extra_help_callback(self, helpfile):
577 "Create a callback with the helpfile value frozen at definition time"
578 def display_extra_help(helpfile=helpfile):
579 self.display_docs(helpfile)
580 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000581
Steven M. Gava1d46e402002-03-27 08:40:46 +0000582 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000583 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000584 rfList=[]
585 if os.path.exists(self.recentFilesPath):
586 RFfile=open(self.recentFilesPath,'r')
587 try:
588 rfList=RFfile.readlines()
589 finally:
590 RFfile.close()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000591 if newFile:
Steven M. Gava1d46e402002-03-27 08:40:46 +0000592 newFile=os.path.abspath(newFile)+'\n'
593 if newFile in rfList:
594 rfList.remove(newFile)
595 rfList.insert(0,newFile)
596 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000597 #print self.flist.inversedict
598 #print self.top.instanceDict
599 #print self
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000600 ullist = "1234567890ABCDEFGHIJ"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000601 if rfList:
602 for instance in self.top.instanceDict.keys():
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000603 menu = instance.menuRecentFiles
604 menu.delete(1,END)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000605 i = 0 ; ul = 0; ullen = len(ullist)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000606 for file in rfList:
607 fileName=file[0:-1]
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000608 callback = instance.__RecentFileCallback(fileName)
609 if i > ullen: # don't underline menuitems
610 ul=None
611 menu.add_command(label=ullist[i] + " " + fileName,
612 command=callback,
613 underline=ul)
614 i += 1
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000615
Steven M. Gava1d46e402002-03-27 08:40:46 +0000616 def __CleanRecentFiles(self,rfList):
617 origRfList=rfList[:]
618 count=0
619 nonFiles=[]
620 for path in rfList:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000621 if not os.path.exists(path[0:-1]):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000622 nonFiles.append(count)
623 count=count+1
624 if nonFiles:
625 nonFiles.reverse()
626 for index in nonFiles:
627 del(rfList[index])
628 if len(rfList)>19:
629 rfList=rfList[0:19]
630 #if rfList != origRfList:
631 RFfile=open(self.recentFilesPath,'w')
632 try:
633 RFfile.writelines(rfList)
634 finally:
635 RFfile.close()
636 return rfList
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000637
Steven M. Gava1d46e402002-03-27 08:40:46 +0000638 def __RecentFileCallback(self,fileName):
639 def OpenRecentFile(fileName=fileName):
640 self.io.open(editFile=fileName)
641 return OpenRecentFile
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000642
David Scherer7aced172000-08-15 01:13:23 +0000643 def saved_change_hook(self):
644 short = self.short_title()
645 long = self.long_title()
646 if short and long:
647 title = short + " - " + long
648 elif short:
649 title = short
650 elif long:
651 title = long
652 else:
653 title = "Untitled"
654 icon = short or long or title
655 if not self.get_saved():
656 title = "*%s*" % title
657 icon = "*%s" % icon
658 self.top.wm_title(title)
659 self.top.wm_iconname(icon)
660
661 def get_saved(self):
662 return self.undo.get_saved()
663
664 def set_saved(self, flag):
665 self.undo.set_saved(flag)
666
667 def reset_undo(self):
668 self.undo.reset_undo()
669
670 def short_title(self):
671 filename = self.io.filename
672 if filename:
673 filename = os.path.basename(filename)
674 return filename
675
676 def long_title(self):
677 return self.io.filename or ""
678
679 def center_insert_event(self, event):
680 self.center()
681
682 def center(self, mark="insert"):
683 text = self.text
684 top, bot = self.getwindowlines()
685 lineno = self.getlineno(mark)
686 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000687 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000688 text.yview(float(newtop))
689
690 def getwindowlines(self):
691 text = self.text
692 top = self.getlineno("@0,0")
693 bot = self.getlineno("@0,65535")
694 if top == bot and text.winfo_height() == 1:
695 # Geometry manager hasn't run yet
696 height = int(text['height'])
697 bot = top + height - 1
698 return top, bot
699
700 def getlineno(self, mark="insert"):
701 text = self.text
702 return int(float(text.index(mark)))
703
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000704 def get_geometry(self):
705 "Return (width, height, x, y)"
706 geom = self.top.wm_geometry()
707 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
708 tuple = (map(int, m.groups()))
709 return tuple
710
David Scherer7aced172000-08-15 01:13:23 +0000711 def close_event(self, event):
712 self.close()
713
714 def maybesave(self):
715 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000716 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000717 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000718 self.top.deiconify()
719 self.top.lower()
720 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000721 return self.io.maybesave()
722
723 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000724 reply = self.maybesave()
725 if reply != "cancel":
726 self._close()
727 return reply
728
729 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000730 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000731 if self.io.filename:
732 self.UpdateRecentFilesList(newFile=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000733 WindowList.unregister_callback(self.postwindowsmenu)
734 if self.close_hook:
735 self.close_hook()
736 self.flist = None
737 colorizing = 0
738 self.unload_extensions()
739 self.io.close(); self.io = None
740 self.undo = None # XXX
741 if self.color:
742 colorizing = self.color.colorizing
743 doh = colorizing and self.top
744 self.color.close(doh) # Cancel colorization
745 self.text = None
746 self.vars = None
747 self.per.close(); self.per = None
748 if not colorizing:
749 self.top.destroy()
750
751 def load_extensions(self):
752 self.extensions = {}
753 self.load_standard_extensions()
754
755 def unload_extensions(self):
756 for ins in self.extensions.values():
757 if hasattr(ins, "close"):
758 ins.close()
759 self.extensions = {}
760
761 def load_standard_extensions(self):
762 for name in self.get_standard_extension_names():
763 try:
764 self.load_extension(name)
765 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000766 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +0000767 import traceback
768 traceback.print_exc()
769
770 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000771 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000772
773 def load_extension(self, name):
774 mod = __import__(name, globals(), locals(), [])
775 cls = getattr(mod, name)
776 ins = cls(self)
777 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000778 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000779 if keydefs:
780 self.apply_bindings(keydefs)
781 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000782 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000783 while methodname[:1] == '<':
784 methodname = methodname[1:]
785 while methodname[-1:] == '>':
786 methodname = methodname[:-1]
787 methodname = methodname + "_event"
788 if hasattr(ins, methodname):
789 self.text.bind(vevent, getattr(ins, methodname))
790 if hasattr(ins, "menudefs"):
791 self.fill_menus(ins.menudefs, keydefs)
792 return ins
793
794 def apply_bindings(self, keydefs=None):
795 if keydefs is None:
796 keydefs = self.Bindings.default_keydefs
797 text = self.text
798 text.keydefs = keydefs
799 for event, keylist in keydefs.items():
800 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000801 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000802
803 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000804 """Add appropriate entries to the menus and submenus
805
806 Menus that are absent or None in self.menudict are ignored.
807 """
David Scherer7aced172000-08-15 01:13:23 +0000808 if defs is None:
809 defs = self.Bindings.menudefs
810 if keydefs is None:
811 keydefs = self.Bindings.default_keydefs
812 menudict = self.menudict
813 text = self.text
814 for mname, itemlist in defs:
815 menu = menudict.get(mname)
816 if not menu:
817 continue
818 for item in itemlist:
819 if not item:
820 menu.add_separator()
821 else:
822 label, event = item
823 checkbutton = (label[:1] == '!')
824 if checkbutton:
825 label = label[1:]
826 underline, label = prepstr(label)
827 accelerator = get_accelerator(keydefs, event)
828 def command(text=text, event=event):
829 text.event_generate(event)
830 if checkbutton:
831 var = self.getrawvar(event, BooleanVar)
832 menu.add_checkbutton(label=label, underline=underline,
833 command=command, accelerator=accelerator,
834 variable=var)
835 else:
836 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000837 command=command,
838 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000839
840 def getvar(self, name):
841 var = self.getrawvar(name)
842 if var:
843 return var.get()
844
845 def setvar(self, name, value, vartype=None):
846 var = self.getrawvar(name, vartype)
847 if var:
848 var.set(value)
849
850 def getrawvar(self, name, vartype=None):
851 var = self.vars.get(name)
852 if not var and vartype:
853 self.vars[name] = var = vartype(self.text)
854 return var
855
856 # Tk implementations of "virtual text methods" -- each platform
857 # reusing IDLE's support code needs to define these for its GUI's
858 # flavor of widget.
859
860 # Is character at text_index in a Python string? Return 0 for
861 # "guaranteed no", true for anything else. This info is expensive
862 # to compute ab initio, but is probably already known by the
863 # platform's colorizer.
864
865 def is_char_in_string(self, text_index):
866 if self.color:
867 # Return true iff colorizer hasn't (re)gotten this far
868 # yet, or the character is tagged as being in a string
869 return self.text.tag_prevrange("TODO", text_index) or \
870 "STRING" in self.text.tag_names(text_index)
871 else:
872 # The colorizer is missing: assume the worst
873 return 1
874
875 # If a selection is defined in the text widget, return (start,
876 # end) as Tkinter text indices, otherwise return (None, None)
877 def get_selection_indices(self):
878 try:
879 first = self.text.index("sel.first")
880 last = self.text.index("sel.last")
881 return first, last
882 except TclError:
883 return None, None
884
885 # Return the text widget's current view of what a tab stop means
886 # (equivalent width in spaces).
887
888 def get_tabwidth(self):
889 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
890 return int(current)
891
892 # Set the text widget's current view of what a tab stop means.
893
894 def set_tabwidth(self, newtabwidth):
895 text = self.text
896 if self.get_tabwidth() != newtabwidth:
897 pixels = text.tk.call("font", "measure", text["font"],
898 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000899 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000900 text.configure(tabs=pixels)
901
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000902### begin autoindent code ###
903
904 # usetabs true -> literal tab characters are used by indent and
905 # dedent cmds, possibly mixed with spaces if
906 # indentwidth is not a multiple of tabwidth
907 # false -> tab characters are converted to spaces by indent
908 # and dedent cmds, and ditto TAB keystrokes
909 # indentwidth is the number of characters per logical indent level.
910 # tabwidth is the display width of a literal tab character.
911 # CAUTION: telling Tk to use anything other than its default
912 # tab setting causes it to use an entirely different tabbing algorithm,
913 # treating tab stops as fixed distances from the left margin.
914 # Nobody expects this, so for now tabwidth should never be changed.
915 usetabs = 0
916 indentwidth = 4
917 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
918
919 # If context_use_ps1 is true, parsing searches back for a ps1 line;
920 # else searches for a popular (if, def, ...) Python stmt.
921 context_use_ps1 = 0
922
923 # When searching backwards for a reliable place to begin parsing,
924 # first start num_context_lines[0] lines back, then
925 # num_context_lines[1] lines back if that didn't work, and so on.
926 # The last value should be huge (larger than the # of lines in a
927 # conceivable file).
928 # Making the initial values larger slows things down more often.
929 num_context_lines = 50, 500, 5000000
930
931 def config(self, **options):
932 for key, value in options.items():
933 if key == 'usetabs':
934 self.usetabs = value
935 elif key == 'indentwidth':
936 self.indentwidth = value
937 elif key == 'tabwidth':
938 self.tabwidth = value
939 elif key == 'context_use_ps1':
940 self.context_use_ps1 = value
941 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000942 raise KeyError, "bad option name: %r" % (key,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000943
944 # If ispythonsource and guess are true, guess a good value for
945 # indentwidth based on file content (if possible), and if
946 # indentwidth != tabwidth set usetabs false.
947 # In any case, adjust the Text widget's view of what a tab
948 # character means.
949
950 def set_indentation_params(self, ispythonsource, guess=1):
951 if guess and ispythonsource:
952 i = self.guess_indent()
953 if 2 <= i <= 8:
954 self.indentwidth = i
955 if self.indentwidth != self.tabwidth:
956 self.usetabs = 0
957
958 self.set_tabwidth(self.tabwidth)
959
960 def smart_backspace_event(self, event):
961 text = self.text
962 first, last = self.get_selection_indices()
963 if first and last:
964 text.delete(first, last)
965 text.mark_set("insert", first)
966 return "break"
967 # Delete whitespace left, until hitting a real char or closest
968 # preceding virtual tab stop.
969 chars = text.get("insert linestart", "insert")
970 if chars == '':
971 if text.compare("insert", ">", "1.0"):
972 # easy: delete preceding newline
973 text.delete("insert-1c")
974 else:
975 text.bell() # at start of buffer
976 return "break"
977 if chars[-1] not in " \t":
978 # easy: delete preceding real char
979 text.delete("insert-1c")
980 return "break"
981 # Ick. It may require *inserting* spaces if we back up over a
982 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000983 tabwidth = self.tabwidth
984 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000985 assert have > 0
986 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000987 # Debug prompt is multilined....
988 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000989 ncharsdeleted = 0
990 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000991 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +0000992 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000993 chars = chars[:-1]
994 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000995 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000996 if have <= want or chars[-1] not in " \t":
997 break
998 text.undo_block_start()
999 text.delete("insert-%dc" % ncharsdeleted, "insert")
1000 if have < want:
1001 text.insert("insert", ' ' * (want - have))
1002 text.undo_block_stop()
1003 return "break"
1004
1005 def smart_indent_event(self, event):
1006 # if intraline selection:
1007 # delete it
1008 # elif multiline selection:
1009 # do indent-region & return
1010 # indent one level
1011 text = self.text
1012 first, last = self.get_selection_indices()
1013 text.undo_block_start()
1014 try:
1015 if first and last:
1016 if index2line(first) != index2line(last):
1017 return self.indent_region_event(event)
1018 text.delete(first, last)
1019 text.mark_set("insert", first)
1020 prefix = text.get("insert linestart", "insert")
1021 raw, effective = classifyws(prefix, self.tabwidth)
1022 if raw == len(prefix):
1023 # only whitespace to the left
1024 self.reindent_to(effective + self.indentwidth)
1025 else:
1026 if self.usetabs:
1027 pad = '\t'
1028 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001029 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001030 n = self.indentwidth
1031 pad = ' ' * (n - effective % n)
1032 text.insert("insert", pad)
1033 text.see("insert")
1034 return "break"
1035 finally:
1036 text.undo_block_stop()
1037
1038 def newline_and_indent_event(self, event):
1039 text = self.text
1040 first, last = self.get_selection_indices()
1041 text.undo_block_start()
1042 try:
1043 if first and last:
1044 text.delete(first, last)
1045 text.mark_set("insert", first)
1046 line = text.get("insert linestart", "insert")
1047 i, n = 0, len(line)
1048 while i < n and line[i] in " \t":
1049 i = i+1
1050 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001051 # the cursor is in or at leading indentation in a continuation
1052 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001053 text.insert("insert linestart", '\n')
1054 return "break"
1055 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001056 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001057 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001058 last_line_of_prompt = sys.ps1.split('\n')[-1]
1059 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001060 line = line[:-1]
1061 i = i+1
1062 if i:
1063 text.delete("insert - %d chars" % i, "insert")
1064 # strip whitespace after insert point
1065 while text.get("insert") in " \t":
1066 text.delete("insert")
1067 # start new line
1068 text.insert("insert", '\n')
1069
1070 # adjust indentation for continuations and block
1071 # open/close first need to find the last stmt
1072 lno = index2line(text.index('insert'))
1073 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1074 for context in self.num_context_lines:
1075 startat = max(lno - context, 1)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001076 startatindex = repr(startat) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001077 rawtext = text.get(startatindex, "insert")
1078 y.set_str(rawtext)
1079 bod = y.find_good_parse_start(
1080 self.context_use_ps1,
1081 self._build_char_in_string_func(startatindex))
1082 if bod is not None or startat == 1:
1083 break
1084 y.set_lo(bod or 0)
1085 c = y.get_continuation_type()
1086 if c != PyParse.C_NONE:
1087 # The current stmt hasn't ended yet.
1088 if c == PyParse.C_STRING:
1089 # inside a string; just mimic the current indent
1090 text.insert("insert", indent)
1091 elif c == PyParse.C_BRACKET:
1092 # line up with the first (if any) element of the
1093 # last open bracket structure; else indent one
1094 # level beyond the indent of the line with the
1095 # last open bracket
1096 self.reindent_to(y.compute_bracket_indent())
1097 elif c == PyParse.C_BACKSLASH:
1098 # if more than one line in this stmt already, just
1099 # mimic the current indent; else if initial line
1100 # has a start on an assignment stmt, indent to
1101 # beyond leftmost =; else to beyond first chunk of
1102 # non-whitespace on initial line
1103 if y.get_num_lines_in_stmt() > 1:
1104 text.insert("insert", indent)
1105 else:
1106 self.reindent_to(y.compute_backslash_indent())
1107 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001108 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001109 return "break"
1110
1111 # This line starts a brand new stmt; indent relative to
1112 # indentation of initial line of closest preceding
1113 # interesting stmt.
1114 indent = y.get_base_indent_string()
1115 text.insert("insert", indent)
1116 if y.is_block_opener():
1117 self.smart_indent_event(event)
1118 elif indent and y.is_block_closer():
1119 self.smart_backspace_event(event)
1120 return "break"
1121 finally:
1122 text.see("insert")
1123 text.undo_block_stop()
1124
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001125 # Our editwin provides a is_char_in_string function that works
1126 # with a Tk text index, but PyParse only knows about offsets into
1127 # a string. This builds a function for PyParse that accepts an
1128 # offset.
1129
1130 def _build_char_in_string_func(self, startindex):
1131 def inner(offset, _startindex=startindex,
1132 _icis=self.is_char_in_string):
1133 return _icis(_startindex + "+%dc" % offset)
1134 return inner
1135
1136 def indent_region_event(self, event):
1137 head, tail, chars, lines = self.get_region()
1138 for pos in range(len(lines)):
1139 line = lines[pos]
1140 if line:
1141 raw, effective = classifyws(line, self.tabwidth)
1142 effective = effective + self.indentwidth
1143 lines[pos] = self._make_blanks(effective) + line[raw:]
1144 self.set_region(head, tail, chars, lines)
1145 return "break"
1146
1147 def dedent_region_event(self, event):
1148 head, tail, chars, lines = self.get_region()
1149 for pos in range(len(lines)):
1150 line = lines[pos]
1151 if line:
1152 raw, effective = classifyws(line, self.tabwidth)
1153 effective = max(effective - self.indentwidth, 0)
1154 lines[pos] = self._make_blanks(effective) + line[raw:]
1155 self.set_region(head, tail, chars, lines)
1156 return "break"
1157
1158 def comment_region_event(self, event):
1159 head, tail, chars, lines = self.get_region()
1160 for pos in range(len(lines) - 1):
1161 line = lines[pos]
1162 lines[pos] = '##' + line
1163 self.set_region(head, tail, chars, lines)
1164
1165 def uncomment_region_event(self, event):
1166 head, tail, chars, lines = self.get_region()
1167 for pos in range(len(lines)):
1168 line = lines[pos]
1169 if not line:
1170 continue
1171 if line[:2] == '##':
1172 line = line[2:]
1173 elif line[:1] == '#':
1174 line = line[1:]
1175 lines[pos] = line
1176 self.set_region(head, tail, chars, lines)
1177
1178 def tabify_region_event(self, event):
1179 head, tail, chars, lines = self.get_region()
1180 tabwidth = self._asktabwidth()
1181 for pos in range(len(lines)):
1182 line = lines[pos]
1183 if line:
1184 raw, effective = classifyws(line, tabwidth)
1185 ntabs, nspaces = divmod(effective, tabwidth)
1186 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1187 self.set_region(head, tail, chars, lines)
1188
1189 def untabify_region_event(self, event):
1190 head, tail, chars, lines = self.get_region()
1191 tabwidth = self._asktabwidth()
1192 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001193 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001194 self.set_region(head, tail, chars, lines)
1195
1196 def toggle_tabs_event(self, event):
1197 if self.askyesno(
1198 "Toggle tabs",
1199 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1200 parent=self.text):
1201 self.usetabs = not self.usetabs
1202 return "break"
1203
1204 # XXX this isn't bound to anything -- see class tabwidth comments
1205 def change_tabwidth_event(self, event):
1206 new = self._asktabwidth()
1207 if new != self.tabwidth:
1208 self.tabwidth = new
1209 self.set_indentation_params(0, guess=0)
1210 return "break"
1211
1212 def change_indentwidth_event(self, event):
1213 new = self.askinteger(
1214 "Indent width",
1215 "New indent width (2-16)",
1216 parent=self.text,
1217 initialvalue=self.indentwidth,
1218 minvalue=2,
1219 maxvalue=16)
1220 if new and new != self.indentwidth:
1221 self.indentwidth = new
1222 return "break"
1223
1224 def get_region(self):
1225 text = self.text
1226 first, last = self.get_selection_indices()
1227 if first and last:
1228 head = text.index(first + " linestart")
1229 tail = text.index(last + "-1c lineend +1c")
1230 else:
1231 head = text.index("insert linestart")
1232 tail = text.index("insert lineend +1c")
1233 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001234 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001235 return head, tail, chars, lines
1236
1237 def set_region(self, head, tail, chars, lines):
1238 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001239 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001240 if newchars == chars:
1241 text.bell()
1242 return
1243 text.tag_remove("sel", "1.0", "end")
1244 text.mark_set("insert", head)
1245 text.undo_block_start()
1246 text.delete(head, tail)
1247 text.insert(head, newchars)
1248 text.undo_block_stop()
1249 text.tag_add("sel", head, "insert")
1250
1251 # Make string that displays as n leading blanks.
1252
1253 def _make_blanks(self, n):
1254 if self.usetabs:
1255 ntabs, nspaces = divmod(n, self.tabwidth)
1256 return '\t' * ntabs + ' ' * nspaces
1257 else:
1258 return ' ' * n
1259
1260 # Delete from beginning of line to insert point, then reinsert
1261 # column logical (meaning use tabs if appropriate) spaces.
1262
1263 def reindent_to(self, column):
1264 text = self.text
1265 text.undo_block_start()
1266 if text.compare("insert linestart", "!=", "insert"):
1267 text.delete("insert linestart", "insert")
1268 if column:
1269 text.insert("insert", self._make_blanks(column))
1270 text.undo_block_stop()
1271
1272 def _asktabwidth(self):
1273 return self.askinteger(
1274 "Tab width",
1275 "Spaces per tab? (2-16)",
1276 parent=self.text,
1277 initialvalue=self.indentwidth,
1278 minvalue=2,
1279 maxvalue=16) or self.tabwidth
1280
1281 # Guess indentwidth from text content.
1282 # Return guessed indentwidth. This should not be believed unless
1283 # it's in a reasonable range (e.g., it will be 0 if no indented
1284 # blocks are found).
1285
1286 def guess_indent(self):
1287 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1288 if opener and indented:
1289 raw, indentsmall = classifyws(opener, self.tabwidth)
1290 raw, indentlarge = classifyws(indented, self.tabwidth)
1291 else:
1292 indentsmall = indentlarge = 0
1293 return indentlarge - indentsmall
1294
1295# "line.col" -> line, as an int
1296def index2line(index):
1297 return int(float(index))
1298
1299# Look at the leading whitespace in s.
1300# Return pair (# of leading ws characters,
1301# effective # of leading blanks after expanding
1302# tabs to width tabwidth)
1303
1304def classifyws(s, tabwidth):
1305 raw = effective = 0
1306 for ch in s:
1307 if ch == ' ':
1308 raw = raw + 1
1309 effective = effective + 1
1310 elif ch == '\t':
1311 raw = raw + 1
1312 effective = (effective // tabwidth + 1) * tabwidth
1313 else:
1314 break
1315 return raw, effective
1316
1317import tokenize
1318_tokenize = tokenize
1319del tokenize
1320
1321class IndentSearcher:
1322
1323 # .run() chews over the Text widget, looking for a block opener
1324 # and the stmt following it. Returns a pair,
1325 # (line containing block opener, line containing stmt)
1326 # Either or both may be None.
1327
1328 def __init__(self, text, tabwidth):
1329 self.text = text
1330 self.tabwidth = tabwidth
1331 self.i = self.finished = 0
1332 self.blkopenline = self.indentedline = None
1333
1334 def readline(self):
1335 if self.finished:
1336 return ""
1337 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001338 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001339 if self.text.compare(mark, ">=", "end"):
1340 return ""
1341 return self.text.get(mark, mark + " lineend+1c")
1342
1343 def tokeneater(self, type, token, start, end, line,
1344 INDENT=_tokenize.INDENT,
1345 NAME=_tokenize.NAME,
1346 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1347 if self.finished:
1348 pass
1349 elif type == NAME and token in OPENERS:
1350 self.blkopenline = line
1351 elif type == INDENT and self.blkopenline:
1352 self.indentedline = line
1353 self.finished = 1
1354
1355 def run(self):
1356 save_tabsize = _tokenize.tabsize
1357 _tokenize.tabsize = self.tabwidth
1358 try:
1359 try:
1360 _tokenize.tokenize(self.readline, self.tokeneater)
1361 except _tokenize.TokenError:
1362 # since we cut off the tokenizer early, we can trigger
1363 # spurious errors
1364 pass
1365 finally:
1366 _tokenize.tabsize = save_tabsize
1367 return self.blkopenline, self.indentedline
1368
1369### end autoindent code ###
1370
David Scherer7aced172000-08-15 01:13:23 +00001371def prepstr(s):
1372 # Helper to extract the underscore from a string, e.g.
1373 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001374 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001375 if i >= 0:
1376 s = s[:i] + s[i+1:]
1377 return i, s
1378
1379
1380keynames = {
1381 'bracketleft': '[',
1382 'bracketright': ']',
1383 'slash': '/',
1384}
1385
1386def get_accelerator(keydefs, event):
1387 keylist = keydefs.get(event)
1388 if not keylist:
1389 return ""
1390 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001391 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001392 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1393 s = re.sub("Key-", "", s)
1394 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1395 s = re.sub("Control-", "Ctrl-", s)
1396 s = re.sub("-", "+", s)
1397 s = re.sub("><", " ", s)
1398 s = re.sub("<", "", s)
1399 s = re.sub(">", "", s)
1400 return s
1401
1402
1403def fixwordbreaks(root):
1404 # Make sure that Tk's double-click and next/previous word
1405 # operations use our definition of a word (i.e. an identifier)
1406 tk = root.tk
1407 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1408 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1409 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1410
1411
1412def test():
1413 root = Tk()
1414 fixwordbreaks(root)
1415 root.withdraw()
1416 if sys.argv[1:]:
1417 filename = sys.argv[1]
1418 else:
1419 filename = None
1420 edit = EditorWindow(root=root, filename=filename)
1421 edit.set_close_hook(root.quit)
1422 root.mainloop()
1423 root.destroy()
1424
1425if __name__ == '__main__':
1426 test()