blob: 9535261f5c0be7395baa2748ca2bb03f7104c542 [file] [log] [blame]
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +00001import sys
2import os
3import string
Guido van Rossum07ec8961999-01-28 22:02:47 +00004import re
Guido van Rossumb3418881998-10-13 03:45:15 +00005import imp
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +00006from Tkinter import *
Guido van Rossumb3418881998-10-13 03:45:15 +00007import tkSimpleDialog
Guido van Rossum2aeeb551998-10-12 21:01:37 +00008import tkMessageBox
Fred Drake6e065312001-04-18 18:42:48 +00009
10import webbrowser
Guido van Rossum504b0bf1999-01-02 21:28:54 +000011import idlever
Guido van Rossumc4f752f1999-02-17 17:20:50 +000012import WindowList
Jeremy Hyltonae1f3bd2000-03-07 17:56:27 +000013from IdleConf import idleconf
Guido van Rossum99aabe32000-02-17 16:14:16 +000014
Guido van Rossum13205601999-06-11 15:03:00 +000015# The default tab setting for a Text widget, in average-width characters.
16TK_TABWIDTH_DEFAULT = 8
17
Guido van Rossum504b0bf1999-01-02 21:28:54 +000018# File menu
19
20#$ event <<open-module>>
21#$ win <Alt-m>
22#$ unix <Control-x><Control-m>
23
24#$ event <<open-class-browser>>
25#$ win <Alt-c>
26#$ unix <Control-x><Control-b>
27
Guido van Rossumd6e87131999-03-10 05:18:02 +000028#$ event <<open-path-browser>>
29
Guido van Rossum504b0bf1999-01-02 21:28:54 +000030#$ event <<close-window>>
Fred Drake36377992000-07-09 19:10:19 +000031
Guido van Rossum504b0bf1999-01-02 21:28:54 +000032#$ unix <Control-x><Control-0>
33#$ unix <Control-x><Key-0>
34#$ win <Alt-F4>
35
36# Edit menu
37
38#$ event <<Copy>>
39#$ win <Control-c>
40#$ unix <Alt-w>
41
42#$ event <<Cut>>
43#$ win <Control-x>
44#$ unix <Control-w>
45
46#$ event <<Paste>>
47#$ win <Control-v>
48#$ unix <Control-y>
49
50#$ event <<select-all>>
51#$ win <Alt-a>
52#$ unix <Alt-a>
53
54# Help menu
55
56#$ event <<help>>
57#$ win <F1>
58#$ unix <F1>
59
60#$ event <<about-idle>>
61
62# Events without menu entries
63
64#$ event <<remove-selection>>
65#$ win <Escape>
66
67#$ event <<center-insert>>
68#$ win <Control-l>
69#$ unix <Control-l>
70
71#$ event <<do-nothing>>
72#$ unix <Control-x>
73
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000074
Guido van Rossum2aeeb551998-10-12 21:01:37 +000075about_title = "About IDLE"
76about_text = """\
Guido van Rossum504b0bf1999-01-02 21:28:54 +000077IDLE %s
Guido van Rossum2aeeb551998-10-12 21:01:37 +000078
Guido van Rossum504b0bf1999-01-02 21:28:54 +000079An Integrated DeveLopment Environment for Python
Guido van Rossum2aeeb551998-10-12 21:01:37 +000080
81by Guido van Rossum
Guido van Rossum504b0bf1999-01-02 21:28:54 +000082""" % idlever.IDLE_VERSION
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000083
84class EditorWindow:
85
86 from Percolator import Percolator
87 from ColorDelegator import ColorDelegator
88 from UndoDelegator import UndoDelegator
89 from IOBinding import IOBinding
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000090 import Bindings
Guido van Rossum504b0bf1999-01-02 21:28:54 +000091 from Tkinter import Toplevel
Guido van Rossumec73dc62000-02-15 18:05:15 +000092 from MultiStatusBar import MultiStatusBar
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000093
Guido van Rossum504b0bf1999-01-02 21:28:54 +000094 about_title = about_title
95 about_text = about_text
96
Guido van Rossumb7ebb831999-01-28 22:24:30 +000097 vars = {}
Raymond Hettinger7f7d5bf2002-05-21 17:00:20 +000098 runnable = False # Shell window cannot Import Module or Run Script
Guido van Rossumb7ebb831999-01-28 22:24:30 +000099
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000100 def __init__(self, flist=None, filename=None, key=None, root=None):
Jeremy Hyltonae1f3bd2000-03-07 17:56:27 +0000101 edconf = idleconf.getsection('EditorWindow')
102 coconf = idleconf.getsection('Colors')
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000103 self.flist = flist
104 root = root or flist.root
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000105 self.root = root
Guido van Rossumb7ebb831999-01-28 22:24:30 +0000106 if flist:
107 self.vars = flist.vars
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000108 self.menubar = Menu(root)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000109 self.top = top = self.Toplevel(root, menu=self.menubar)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000110 self.vbar = vbar = Scrollbar(top, name='vbar')
Guido van Rossumec73dc62000-02-15 18:05:15 +0000111 self.text_frame = text_frame = Frame(top)
112 self.text = text = Text(text_frame, name='text', padx=5,
Jeremy Hyltone81f28b2000-03-03 23:06:45 +0000113 foreground=coconf.getdef('normal-foreground'),
114 background=coconf.getdef('normal-background'),
115 highlightcolor=coconf.getdef('hilite-foreground'),
116 highlightbackground=coconf.getdef('hilite-background'),
117 insertbackground=coconf.getdef('cursor-background'),
118 width=edconf.getint('width'),
119 height=edconf.getint('height'),
120 wrap="none")
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000121
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000122 self.createmenubar()
Guido van Rossum07ec8961999-01-28 22:02:47 +0000123 self.apply_bindings()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000124
125 self.top.protocol("WM_DELETE_WINDOW", self.close)
126 self.top.bind("<<close-window>>", self.close_event)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000127 text.bind("<<center-insert>>", self.center_insert_event)
128 text.bind("<<help>>", self.help_dialog)
Guido van Rossum416b9611999-08-26 23:06:05 +0000129 text.bind("<<python-docs>>", self.python_docs)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000130 text.bind("<<about-idle>>", self.about_dialog)
131 text.bind("<<open-module>>", self.open_module)
132 text.bind("<<do-nothing>>", lambda event: "break")
133 text.bind("<<select-all>>", self.select_all)
134 text.bind("<<remove-selection>>", self.remove_selection)
135 text.bind("<3>", self.right_menu_event)
136 if flist:
137 flist.inversedict[self] = key
138 if key:
139 flist.dict[key] = self
140 text.bind("<<open-new-window>>", self.flist.new_callback)
141 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
142 text.bind("<<open-class-browser>>", self.open_class_browser)
Guido van Rossumd6e87131999-03-10 05:18:02 +0000143 text.bind("<<open-path-browser>>", self.open_path_browser)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000144
145 vbar['command'] = text.yview
146 vbar.pack(side=RIGHT, fill=Y)
147
148 text['yscrollcommand'] = vbar.set
Jeremy Hyltone81f28b2000-03-03 23:06:45 +0000149 text['font'] = edconf.get('font-name'), edconf.get('font-size')
Guido van Rossumec73dc62000-02-15 18:05:15 +0000150 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
151 text.pack(side=TOP, fill=BOTH, expand=1)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000152 text.focus_set()
153
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000154 self.per = per = self.Percolator(text)
155 if self.ispythonsource(filename):
156 self.color = color = self.ColorDelegator(); per.insertfilter(color)
157 ##print "Initial colorizer"
158 else:
159 ##print "No initial colorizer"
160 self.color = None
161 self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000162 self.io = io = self.IOBinding(self)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000163
Guido van Rossum318a70d1999-05-03 15:49:52 +0000164 text.undo_block_start = undo.undo_block_start
165 text.undo_block_stop = undo.undo_block_stop
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000166 undo.set_saved_change_hook(self.saved_change_hook)
167 io.set_filename_change_hook(self.filename_change_hook)
168
169 if filename:
170 if os.path.exists(filename):
171 io.loadfile(filename)
172 else:
173 io.set_filename(filename)
174
175 self.saved_change_hook()
176
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000177 self.load_extensions()
178
179 menu = self.menudict.get('windows')
180 if menu:
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000181 end = menu.index("end")
182 if end is None:
183 end = -1
184 if end >= 0:
185 menu.add_separator()
186 end = end + 1
187 self.wmenu_end = end
Guido van Rossumc4f752f1999-02-17 17:20:50 +0000188 WindowList.register_callback(self.postwindowsmenu)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000189
Guido van Rossumd395aee1999-06-02 11:04:29 +0000190 # Some abstractions so IDLE extensions are cross-IDE
191 self.askyesno = tkMessageBox.askyesno
192 self.askinteger = tkSimpleDialog.askinteger
193 self.showerror = tkMessageBox.showerror
194
Guido van Rossumdef2c961999-05-21 04:38:27 +0000195 if self.extensions.has_key('AutoIndent'):
196 self.extensions['AutoIndent'].set_indentation_params(
197 self.ispythonsource(filename))
Guido van Rossumec73dc62000-02-15 18:05:15 +0000198 self.set_status_bar()
199
200 def set_status_bar(self):
201 self.status_bar = self.MultiStatusBar(self.text_frame)
202 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
203 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
204 self.status_bar.pack(side=BOTTOM, fill=X)
205 self.text.bind('<KeyRelease>', self.set_line_and_column)
206 self.text.bind('<ButtonRelease>', self.set_line_and_column)
207 self.text.after_idle(self.set_line_and_column)
208
209 def set_line_and_column(self, event=None):
210 line, column = string.split(self.text.index(INSERT), '.')
211 self.status_bar.set_label('column', 'Col: %s' % column)
212 self.status_bar.set_label('line', 'Ln: %s' % line)
Guido van Rossumdef2c961999-05-21 04:38:27 +0000213
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000214 def wakeup(self):
Guido van Rossum36911a11999-01-18 15:18:57 +0000215 if self.top.wm_state() == "iconic":
216 self.top.wm_deiconify()
217 else:
218 self.top.tkraise()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000219 self.text.focus_set()
220
Guido van Rossume7b2e651998-10-12 23:56:08 +0000221 menu_specs = [
Guido van Rossumb5eed031998-11-27 03:19:07 +0000222 ("file", "_File"),
223 ("edit", "_Edit"),
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000224 ("windows", "_Windows"),
Guido van Rossumb5eed031998-11-27 03:19:07 +0000225 ("help", "_Help"),
Guido van Rossume7b2e651998-10-12 23:56:08 +0000226 ]
227
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000228 def createmenubar(self):
229 mbar = self.menubar
Guido van Rossum07ec8961999-01-28 22:02:47 +0000230 self.menudict = menudict = {}
Guido van Rossume7b2e651998-10-12 23:56:08 +0000231 for name, label in self.menu_specs:
Guido van Rossum07ec8961999-01-28 22:02:47 +0000232 underline, label = prepstr(label)
233 menudict[name] = menu = Menu(mbar, name=name)
Guido van Rossumb5eed031998-11-27 03:19:07 +0000234 mbar.add_cascade(label=label, menu=menu, underline=underline)
Guido van Rossum07ec8961999-01-28 22:02:47 +0000235 self.fill_menus()
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000236
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000237 def postwindowsmenu(self):
238 # Only called when Windows menu exists
Guido van Rossum85a36a51999-06-10 17:43:17 +0000239 # XXX Actually, this Just-In-Time updating interferes badly
240 # XXX with the tear-off feature. It would be better to update
241 # XXX all Windows menus whenever the list of windows changes.
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000242 menu = self.menudict['windows']
243 end = menu.index("end")
244 if end is None:
245 end = -1
246 if end > self.wmenu_end:
247 menu.delete(self.wmenu_end+1, end)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000248 WindowList.add_windows_to_menu(menu)
249
250 rmenu = None
251
252 def right_menu_event(self, event):
253 self.text.tag_remove("sel", "1.0", "end")
254 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
255 if not self.rmenu:
256 self.make_rmenu()
257 rmenu = self.rmenu
258 self.event = event
259 iswin = sys.platform[:3] == 'win'
260 if iswin:
261 self.text.config(cursor="arrow")
262 rmenu.tk_popup(event.x_root, event.y_root)
263 if iswin:
264 self.text.config(cursor="ibeam")
265
266 rmenu_specs = [
267 # ("Label", "<<virtual-event>>"), ...
268 ("Close", "<<close-window>>"), # Example
269 ]
270
271 def make_rmenu(self):
272 rmenu = Menu(self.text, tearoff=0)
273 for label, eventname in self.rmenu_specs:
274 def command(text=self.text, eventname=eventname):
275 text.event_generate(eventname)
276 rmenu.add_command(label=label, command=command)
277 self.rmenu = rmenu
278
Guido van Rossume7b2e651998-10-12 23:56:08 +0000279 def about_dialog(self, event=None):
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000280 tkMessageBox.showinfo(self.about_title, self.about_text,
281 master=self.text)
282
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000283 helpfile = "help.txt"
284
Guido van Rossume7b2e651998-10-12 23:56:08 +0000285 def help_dialog(self, event=None):
Guido van Rossum416b9611999-08-26 23:06:05 +0000286 try:
287 helpfile = os.path.join(os.path.dirname(__file__), self.helpfile)
288 except NameError:
289 helpfile = self.helpfile
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000290 if self.flist:
291 self.flist.open(helpfile)
292 else:
293 self.io.loadfile(helpfile)
294
Guido van Rossum416b9611999-08-26 23:06:05 +0000295 help_url = "http://www.python.org/doc/current/"
Fred Drake8638ace2000-05-10 16:50:07 +0000296 if sys.platform[:3] == "win":
Tim Petersf58a7aa2000-09-22 10:05:54 +0000297 fn = os.path.dirname(__file__)
Martin v. Löwisdcd2dc22002-04-14 10:30:51 +0000298 fn = os.path.join(fn, os.pardir, os.pardir, "pythlp.chm")
Tim Petersf58a7aa2000-09-22 10:05:54 +0000299 fn = os.path.normpath(fn)
Fred Drake8638ace2000-05-10 16:50:07 +0000300 if os.path.isfile(fn):
301 help_url = fn
Martin v. Löwisdcd2dc22002-04-14 10:30:51 +0000302 else:
303 fn = os.path.dirname(__file__)
304 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
305 fn = os.path.normpath(fn)
306 if os.path.isfile(fn):
307 help_url = fn
Fred Drake8638ace2000-05-10 16:50:07 +0000308 del fn
Guido van Rossum416b9611999-08-26 23:06:05 +0000309
Martin v. Löwisdcd2dc22002-04-14 10:30:51 +0000310 def python_docs(self, event=None):
311 os.startfile(self.help_url)
312 else:
313 def python_docs(self, event=None):
314 webbrowser.open(self.help_url)
Guido van Rossum416b9611999-08-26 23:06:05 +0000315
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000316 def select_all(self, event=None):
317 self.text.tag_add("sel", "1.0", "end-1c")
318 self.text.mark_set("insert", "1.0")
319 self.text.see("insert")
320 return "break"
321
322 def remove_selection(self, event=None):
323 self.text.tag_remove("sel", "1.0", "end")
324 self.text.see("insert")
325
Guido van Rossumb3418881998-10-13 03:45:15 +0000326 def open_module(self, event=None):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000327 # XXX Shouldn't this be in IOBinding or in FileList?
Guido van Rossumb3418881998-10-13 03:45:15 +0000328 try:
329 name = self.text.get("sel.first", "sel.last")
330 except TclError:
331 name = ""
332 else:
333 name = string.strip(name)
334 if not name:
335 name = tkSimpleDialog.askstring("Module",
Guido van Rossume1dedc01998-10-16 16:09:57 +0000336 "Enter the name of a Python module\n"
337 "to search on sys.path and open:",
Guido van Rossumb3418881998-10-13 03:45:15 +0000338 parent=self.text)
339 if name:
340 name = string.strip(name)
341 if not name:
342 return
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000343 # XXX Ought to support package syntax
344 # XXX Ought to insert current file's directory in front of path
Guido van Rossumb3418881998-10-13 03:45:15 +0000345 try:
346 (f, file, (suffix, mode, type)) = imp.find_module(name)
Guido van Rossum74311b21999-06-01 18:27:14 +0000347 except (NameError, ImportError), msg:
Guido van Rossumb3418881998-10-13 03:45:15 +0000348 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
349 return
350 if type != imp.PY_SOURCE:
351 tkMessageBox.showerror("Unsupported type",
352 "%s is not a source module" % name, parent=self.text)
353 return
354 if f:
355 f.close()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000356 if self.flist:
357 self.flist.open(file)
358 else:
359 self.io.loadfile(file)
360
361 def open_class_browser(self, event=None):
362 filename = self.io.filename
363 if not filename:
364 tkMessageBox.showerror(
365 "No filename",
366 "This buffer has no associated filename",
367 master=self.text)
Guido van Rossum74311b21999-06-01 18:27:14 +0000368 self.text.focus_set()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000369 return None
370 head, tail = os.path.split(filename)
371 base, ext = os.path.splitext(tail)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000372 import ClassBrowser
373 ClassBrowser.ClassBrowser(self.flist, base, [head])
Guido van Rossumdef2c961999-05-21 04:38:27 +0000374
Guido van Rossumd6e87131999-03-10 05:18:02 +0000375 def open_path_browser(self, event=None):
376 import PathBrowser
377 PathBrowser.PathBrowser(self.flist)
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000378
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000379 def gotoline(self, lineno):
380 if lineno is not None and lineno > 0:
381 self.text.mark_set("insert", "%d.0" % lineno)
382 self.text.tag_remove("sel", "1.0", "end")
383 self.text.tag_add("sel", "insert", "insert +1l")
384 self.center()
385
386 def ispythonsource(self, filename):
387 if not filename:
Tim Petersbc0e9102002-04-04 22:55:58 +0000388 return True
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000389 base, ext = os.path.splitext(os.path.basename(filename))
390 if os.path.normcase(ext) in (".py", ".pyw"):
Tim Petersbc0e9102002-04-04 22:55:58 +0000391 return True
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000392 try:
393 f = open(filename)
394 line = f.readline()
395 f.close()
396 except IOError:
Tim Petersbc0e9102002-04-04 22:55:58 +0000397 return False
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000398 return line[:2] == '#!' and string.find(line, 'python') >= 0
399
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000400 def close_hook(self):
401 if self.flist:
402 self.flist.close_edit(self)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000403
404 def set_close_hook(self, close_hook):
405 self.close_hook = close_hook
406
407 def filename_change_hook(self):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000408 if self.flist:
409 self.flist.filename_changed_edit(self)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000410 self.saved_change_hook()
411 if self.ispythonsource(self.io.filename):
412 self.addcolorizer()
413 else:
414 self.rmcolorizer()
415
416 def addcolorizer(self):
417 if self.color:
418 return
419 ##print "Add colorizer"
420 self.per.removefilter(self.undo)
421 self.color = self.ColorDelegator()
422 self.per.insertfilter(self.color)
423 self.per.insertfilter(self.undo)
424
425 def rmcolorizer(self):
426 if not self.color:
427 return
428 ##print "Remove colorizer"
429 self.per.removefilter(self.undo)
430 self.per.removefilter(self.color)
431 self.color = None
432 self.per.insertfilter(self.undo)
433
434 def saved_change_hook(self):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000435 short = self.short_title()
436 long = self.long_title()
437 if short and long:
438 title = short + " - " + long
439 elif short:
440 title = short
441 elif long:
442 title = long
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000443 else:
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000444 title = "Untitled"
445 icon = short or long or title
446 if not self.get_saved():
447 title = "*%s*" % title
448 icon = "*%s" % icon
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000449 self.top.wm_title(title)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000450 self.top.wm_iconname(icon)
451
452 def get_saved(self):
453 return self.undo.get_saved()
454
455 def set_saved(self, flag):
456 self.undo.set_saved(flag)
457
458 def reset_undo(self):
459 self.undo.reset_undo()
460
461 def short_title(self):
462 filename = self.io.filename
463 if filename:
464 filename = os.path.basename(filename)
465 return filename
466
467 def long_title(self):
468 return self.io.filename or ""
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000469
470 def center_insert_event(self, event):
471 self.center()
472
473 def center(self, mark="insert"):
Guido van Rossum245ddc41999-01-11 14:51:32 +0000474 text = self.text
475 top, bot = self.getwindowlines()
476 lineno = self.getlineno(mark)
477 height = bot - top
Guido van Rossum64e9d612002-01-23 15:15:13 +0000478 newtop = max(1, lineno - height//2)
Guido van Rossum245ddc41999-01-11 14:51:32 +0000479 text.yview(float(newtop))
480
481 def getwindowlines(self):
482 text = self.text
483 top = self.getlineno("@0,0")
484 bot = self.getlineno("@0,65535")
Guido van Rossum5051f4f1999-01-12 22:09:57 +0000485 if top == bot and text.winfo_height() == 1:
486 # Geometry manager hasn't run yet
Guido van Rossum245ddc41999-01-11 14:51:32 +0000487 height = int(text['height'])
488 bot = top + height - 1
489 return top, bot
490
491 def getlineno(self, mark="insert"):
492 text = self.text
493 return int(float(text.index(mark)))
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000494
495 def close_event(self, event):
496 self.close()
497
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000498 def maybesave(self):
499 if self.io:
500 return self.io.maybesave()
501
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000502 def close(self):
503 self.top.wm_deiconify()
504 self.top.tkraise()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000505 reply = self.maybesave()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000506 if reply != "cancel":
Guido van Rossum205afb41999-06-25 16:06:29 +0000507 self._close()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000508 return reply
509
Guido van Rossum205afb41999-06-25 16:06:29 +0000510 def _close(self):
511 WindowList.unregister_callback(self.postwindowsmenu)
512 if self.close_hook:
513 self.close_hook()
514 self.flist = None
515 colorizing = 0
516 self.unload_extensions()
517 self.io.close(); self.io = None
518 self.undo = None # XXX
519 if self.color:
520 colorizing = self.color.colorizing
521 doh = colorizing and self.top
522 self.color.close(doh) # Cancel colorization
523 self.text = None
524 self.vars = None
525 self.per.close(); self.per = None
526 if not colorizing:
527 self.top.destroy()
528
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000529 def load_extensions(self):
530 self.extensions = {}
531 self.load_standard_extensions()
532
Guido van Rossum205afb41999-06-25 16:06:29 +0000533 def unload_extensions(self):
534 for ins in self.extensions.values():
535 if hasattr(ins, "close"):
536 ins.close()
537 self.extensions = {}
538
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000539 def load_standard_extensions(self):
540 for name in self.get_standard_extension_names():
541 try:
542 self.load_extension(name)
543 except:
544 print "Failed to load extension", `name`
545 import traceback
546 traceback.print_exc()
547
548 def get_standard_extension_names(self):
Jeremy Hyltonae1f3bd2000-03-07 17:56:27 +0000549 return idleconf.getextensions()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000550
551 def load_extension(self, name):
Guido van Rossum9dd52091999-04-23 14:01:25 +0000552 mod = __import__(name, globals(), locals(), [])
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000553 cls = getattr(mod, name)
554 ins = cls(self)
555 self.extensions[name] = ins
556 kdnames = ["keydefs"]
557 if sys.platform == 'win32':
558 kdnames.append("windows_keydefs")
559 elif sys.platform == 'mac':
560 kdnames.append("mac_keydefs")
561 else:
562 kdnames.append("unix_keydefs")
563 keydefs = {}
564 for kdname in kdnames:
565 if hasattr(ins, kdname):
566 keydefs.update(getattr(ins, kdname))
567 if keydefs:
Guido van Rossum07ec8961999-01-28 22:02:47 +0000568 self.apply_bindings(keydefs)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000569 for vevent in keydefs.keys():
570 methodname = string.replace(vevent, "-", "_")
571 while methodname[:1] == '<':
572 methodname = methodname[1:]
573 while methodname[-1:] == '>':
574 methodname = methodname[:-1]
575 methodname = methodname + "_event"
576 if hasattr(ins, methodname):
577 self.text.bind(vevent, getattr(ins, methodname))
578 if hasattr(ins, "menudefs"):
Guido van Rossum07ec8961999-01-28 22:02:47 +0000579 self.fill_menus(ins.menudefs, keydefs)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000580 return ins
581
Guido van Rossum07ec8961999-01-28 22:02:47 +0000582 def apply_bindings(self, keydefs=None):
583 if keydefs is None:
584 keydefs = self.Bindings.default_keydefs
585 text = self.text
586 text.keydefs = keydefs
587 for event, keylist in keydefs.items():
588 if keylist:
589 apply(text.event_add, (event,) + tuple(keylist))
590
591 def fill_menus(self, defs=None, keydefs=None):
Guido van Rossum85a36a51999-06-10 17:43:17 +0000592 # Fill the menus. Menus that are absent or None in
593 # self.menudict are ignored.
Guido van Rossum07ec8961999-01-28 22:02:47 +0000594 if defs is None:
595 defs = self.Bindings.menudefs
596 if keydefs is None:
597 keydefs = self.Bindings.default_keydefs
598 menudict = self.menudict
599 text = self.text
600 for mname, itemlist in defs:
601 menu = menudict.get(mname)
602 if not menu:
603 continue
604 for item in itemlist:
605 if not item:
606 menu.add_separator()
607 else:
608 label, event = item
609 checkbutton = (label[:1] == '!')
610 if checkbutton:
611 label = label[1:]
612 underline, label = prepstr(label)
613 accelerator = get_accelerator(keydefs, event)
614 def command(text=text, event=event):
615 text.event_generate(event)
616 if checkbutton:
617 var = self.getrawvar(event, BooleanVar)
618 menu.add_checkbutton(label=label, underline=underline,
619 command=command, accelerator=accelerator,
620 variable=var)
621 else:
622 menu.add_command(label=label, underline=underline,
623 command=command, accelerator=accelerator)
Guido van Rossumdef2c961999-05-21 04:38:27 +0000624
Guido van Rossum07ec8961999-01-28 22:02:47 +0000625 def getvar(self, name):
626 var = self.getrawvar(name)
627 if var:
628 return var.get()
Guido van Rossumdef2c961999-05-21 04:38:27 +0000629
Guido van Rossum07ec8961999-01-28 22:02:47 +0000630 def setvar(self, name, value, vartype=None):
631 var = self.getrawvar(name, vartype)
632 if var:
633 var.set(value)
Guido van Rossumdef2c961999-05-21 04:38:27 +0000634
Guido van Rossum07ec8961999-01-28 22:02:47 +0000635 def getrawvar(self, name, vartype=None):
Guido van Rossumb7ebb831999-01-28 22:24:30 +0000636 var = self.vars.get(name)
637 if not var and vartype:
638 self.vars[name] = var = vartype(self.text)
639 return var
Guido van Rossum07ec8961999-01-28 22:02:47 +0000640
Guido van Rossumf4a15081999-06-03 14:32:16 +0000641 # Tk implementations of "virtual text methods" -- each platform
642 # reusing IDLE's support code needs to define these for its GUI's
643 # flavor of widget.
644
645 # Is character at text_index in a Python string? Return 0 for
Guido van Rossum85a36a51999-06-10 17:43:17 +0000646 # "guaranteed no", true for anything else. This info is expensive
647 # to compute ab initio, but is probably already known by the
648 # platform's colorizer.
Guido van Rossumf4a15081999-06-03 14:32:16 +0000649
650 def is_char_in_string(self, text_index):
651 if self.color:
Guido van Rossum85a36a51999-06-10 17:43:17 +0000652 # Return true iff colorizer hasn't (re)gotten this far
653 # yet, or the character is tagged as being in a string
Guido van Rossumf4a15081999-06-03 14:32:16 +0000654 return self.text.tag_prevrange("TODO", text_index) or \
655 "STRING" in self.text.tag_names(text_index)
656 else:
Guido van Rossum85a36a51999-06-10 17:43:17 +0000657 # The colorizer is missing: assume the worst
Guido van Rossumf4a15081999-06-03 14:32:16 +0000658 return 1
659
Guido van Rossum85a36a51999-06-10 17:43:17 +0000660 # If a selection is defined in the text widget, return (start,
661 # end) as Tkinter text indices, otherwise return (None, None)
Guido van Rossum13205601999-06-11 15:03:00 +0000662 def get_selection_indices(self):
Guido van Rossum85a36a51999-06-10 17:43:17 +0000663 try:
664 first = self.text.index("sel.first")
665 last = self.text.index("sel.last")
666 return first, last
667 except TclError:
668 return None, None
669
Guido van Rossum13205601999-06-11 15:03:00 +0000670 # Return the text widget's current view of what a tab stop means
671 # (equivalent width in spaces).
672
673 def get_tabwidth(self):
674 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
675 return int(current)
676
677 # Set the text widget's current view of what a tab stop means.
678
679 def set_tabwidth(self, newtabwidth):
680 text = self.text
681 if self.get_tabwidth() != newtabwidth:
682 pixels = text.tk.call("font", "measure", text["font"],
683 "-displayof", text.master,
Guido van Rossumbdd90172000-09-20 00:17:39 +0000684 "n" * newtabwidth)
Guido van Rossum13205601999-06-11 15:03:00 +0000685 text.configure(tabs=pixels)
686
Guido van Rossum07ec8961999-01-28 22:02:47 +0000687def prepstr(s):
Guido van Rossum85a36a51999-06-10 17:43:17 +0000688 # Helper to extract the underscore from a string, e.g.
689 # prepstr("Co_py") returns (2, "Copy").
Guido van Rossum07ec8961999-01-28 22:02:47 +0000690 i = string.find(s, '_')
691 if i >= 0:
692 s = s[:i] + s[i+1:]
693 return i, s
694
695
696keynames = {
697 'bracketleft': '[',
698 'bracketright': ']',
699 'slash': '/',
700}
701
702def get_accelerator(keydefs, event):
703 keylist = keydefs.get(event)
704 if not keylist:
705 return ""
706 s = keylist[0]
707 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
708 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
709 s = re.sub("Key-", "", s)
710 s = re.sub("Control-", "Ctrl-", s)
711 s = re.sub("-", "+", s)
712 s = re.sub("><", " ", s)
713 s = re.sub("<", "", s)
714 s = re.sub(">", "", s)
715 return s
716
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000717
718def fixwordbreaks(root):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000719 # Make sure that Tk's double-click and next/previous word
720 # operations use our definition of a word (i.e. an identifier)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000721 tk = root.tk
722 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
723 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
724 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
725
726
727def test():
728 root = Tk()
729 fixwordbreaks(root)
730 root.withdraw()
731 if sys.argv[1:]:
732 filename = sys.argv[1]
733 else:
734 filename = None
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000735 edit = EditorWindow(root=root, filename=filename)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000736 edit.set_close_hook(root.quit)
737 root.mainloop()
738 root.destroy()
739
740if __name__ == '__main__':
741 test()