blob: e8c310a1cc1fa7585bb4d66454c4ed85ccce6be3 [file] [log] [blame]
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +00001import sys
2import os
Guido van Rossum07ec8961999-01-28 22:02:47 +00003import re
Guido van Rossumb3418881998-10-13 03:45:15 +00004import imp
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +00005from Tkinter import *
Guido van Rossumb3418881998-10-13 03:45:15 +00006import tkSimpleDialog
Guido van Rossum2aeeb551998-10-12 21:01:37 +00007import tkMessageBox
Fred Drake6e065312001-04-18 18:42:48 +00008
9import webbrowser
Guido van Rossum504b0bf1999-01-02 21:28:54 +000010import idlever
Guido van Rossumc4f752f1999-02-17 17:20:50 +000011import WindowList
Jeremy Hyltonae1f3bd2000-03-07 17:56:27 +000012from IdleConf import idleconf
Guido van Rossum99aabe32000-02-17 16:14:16 +000013
Guido van Rossum13205601999-06-11 15:03:00 +000014# The default tab setting for a Text widget, in average-width characters.
15TK_TABWIDTH_DEFAULT = 8
16
Guido van Rossum504b0bf1999-01-02 21:28:54 +000017# File menu
18
19#$ event <<open-module>>
20#$ win <Alt-m>
21#$ unix <Control-x><Control-m>
22
23#$ event <<open-class-browser>>
24#$ win <Alt-c>
25#$ unix <Control-x><Control-b>
26
Guido van Rossumd6e87131999-03-10 05:18:02 +000027#$ event <<open-path-browser>>
28
Guido van Rossum504b0bf1999-01-02 21:28:54 +000029#$ event <<close-window>>
Fred Drake36377992000-07-09 19:10:19 +000030
Guido van Rossum504b0bf1999-01-02 21:28:54 +000031#$ unix <Control-x><Control-0>
32#$ unix <Control-x><Key-0>
33#$ win <Alt-F4>
34
35# Edit menu
36
37#$ event <<Copy>>
38#$ win <Control-c>
39#$ unix <Alt-w>
40
41#$ event <<Cut>>
42#$ win <Control-x>
43#$ unix <Control-w>
44
45#$ event <<Paste>>
46#$ win <Control-v>
47#$ unix <Control-y>
48
49#$ event <<select-all>>
50#$ win <Alt-a>
51#$ unix <Alt-a>
52
53# Help menu
54
55#$ event <<help>>
56#$ win <F1>
57#$ unix <F1>
58
59#$ event <<about-idle>>
60
61# Events without menu entries
62
63#$ event <<remove-selection>>
64#$ win <Escape>
65
66#$ event <<center-insert>>
67#$ win <Control-l>
68#$ unix <Control-l>
69
70#$ event <<do-nothing>>
71#$ unix <Control-x>
72
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000073
Guido van Rossum2aeeb551998-10-12 21:01:37 +000074about_title = "About IDLE"
75about_text = """\
Guido van Rossum504b0bf1999-01-02 21:28:54 +000076IDLE %s
Guido van Rossum2aeeb551998-10-12 21:01:37 +000077
Guido van Rossum504b0bf1999-01-02 21:28:54 +000078An Integrated DeveLopment Environment for Python
Guido van Rossum2aeeb551998-10-12 21:01:37 +000079
80by Guido van Rossum
Guido van Rossum504b0bf1999-01-02 21:28:54 +000081""" % idlever.IDLE_VERSION
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000082
Raymond Hettingerb2c729f2002-09-08 03:42:01 +000083def _find_module(fullname, path=None):
84 """Version of imp.find_module() that handles hierarchical module names"""
85
86 file = None
87 for tgt in fullname.split('.'):
88 if file is not None:
89 file.close() # close intermediate files
90 (file, filename, descr) = imp.find_module(tgt, path)
91 if descr[2] == imp.PY_SOURCE:
92 break # find but not load the source file
93 module = imp.load_module(tgt, file, filename, descr)
Raymond Hettingera9cfa552003-01-07 09:55:03 +000094 try:
95 path = module.__path__
96 except AttributeError:
97 raise ImportError, 'No source for module ' + module.__name__
Raymond Hettingerb2c729f2002-09-08 03:42:01 +000098 return file, filename, descr
99
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000100class EditorWindow:
101
102 from Percolator import Percolator
103 from ColorDelegator import ColorDelegator
104 from UndoDelegator import UndoDelegator
105 from IOBinding import IOBinding
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000106 import Bindings
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000107 from Tkinter import Toplevel
Guido van Rossumec73dc62000-02-15 18:05:15 +0000108 from MultiStatusBar import MultiStatusBar
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000109
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000110 about_title = about_title
111 about_text = about_text
112
Guido van Rossumb7ebb831999-01-28 22:24:30 +0000113 vars = {}
Raymond Hettinger7f7d5bf2002-05-21 17:00:20 +0000114 runnable = False # Shell window cannot Import Module or Run Script
Guido van Rossumb7ebb831999-01-28 22:24:30 +0000115
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000116 def __init__(self, flist=None, filename=None, key=None, root=None):
Jeremy Hyltonae1f3bd2000-03-07 17:56:27 +0000117 edconf = idleconf.getsection('EditorWindow')
118 coconf = idleconf.getsection('Colors')
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000119 self.flist = flist
120 root = root or flist.root
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000121 self.root = root
Guido van Rossumb7ebb831999-01-28 22:24:30 +0000122 if flist:
123 self.vars = flist.vars
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000124 self.menubar = Menu(root)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000125 self.top = top = self.Toplevel(root, menu=self.menubar)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000126 self.vbar = vbar = Scrollbar(top, name='vbar')
Guido van Rossumec73dc62000-02-15 18:05:15 +0000127 self.text_frame = text_frame = Frame(top)
128 self.text = text = Text(text_frame, name='text', padx=5,
Jeremy Hyltone81f28b2000-03-03 23:06:45 +0000129 foreground=coconf.getdef('normal-foreground'),
130 background=coconf.getdef('normal-background'),
131 highlightcolor=coconf.getdef('hilite-foreground'),
132 highlightbackground=coconf.getdef('hilite-background'),
133 insertbackground=coconf.getdef('cursor-background'),
134 width=edconf.getint('width'),
135 height=edconf.getint('height'),
136 wrap="none")
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000137
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000138 self.createmenubar()
Guido van Rossum07ec8961999-01-28 22:02:47 +0000139 self.apply_bindings()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000140
141 self.top.protocol("WM_DELETE_WINDOW", self.close)
142 self.top.bind("<<close-window>>", self.close_event)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000143 text.bind("<<center-insert>>", self.center_insert_event)
144 text.bind("<<help>>", self.help_dialog)
Guido van Rossum416b9611999-08-26 23:06:05 +0000145 text.bind("<<python-docs>>", self.python_docs)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000146 text.bind("<<about-idle>>", self.about_dialog)
147 text.bind("<<open-module>>", self.open_module)
148 text.bind("<<do-nothing>>", lambda event: "break")
149 text.bind("<<select-all>>", self.select_all)
150 text.bind("<<remove-selection>>", self.remove_selection)
151 text.bind("<3>", self.right_menu_event)
152 if flist:
153 flist.inversedict[self] = key
154 if key:
155 flist.dict[key] = self
156 text.bind("<<open-new-window>>", self.flist.new_callback)
157 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
158 text.bind("<<open-class-browser>>", self.open_class_browser)
Guido van Rossumd6e87131999-03-10 05:18:02 +0000159 text.bind("<<open-path-browser>>", self.open_path_browser)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000160
161 vbar['command'] = text.yview
162 vbar.pack(side=RIGHT, fill=Y)
163
164 text['yscrollcommand'] = vbar.set
Jeremy Hyltone81f28b2000-03-03 23:06:45 +0000165 text['font'] = edconf.get('font-name'), edconf.get('font-size')
Guido van Rossumec73dc62000-02-15 18:05:15 +0000166 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
167 text.pack(side=TOP, fill=BOTH, expand=1)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000168 text.focus_set()
169
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000170 self.per = per = self.Percolator(text)
171 if self.ispythonsource(filename):
172 self.color = color = self.ColorDelegator(); per.insertfilter(color)
173 ##print "Initial colorizer"
174 else:
175 ##print "No initial colorizer"
176 self.color = None
177 self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000178 self.io = io = self.IOBinding(self)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000179
Guido van Rossum318a70d1999-05-03 15:49:52 +0000180 text.undo_block_start = undo.undo_block_start
181 text.undo_block_stop = undo.undo_block_stop
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000182 undo.set_saved_change_hook(self.saved_change_hook)
183 io.set_filename_change_hook(self.filename_change_hook)
184
185 if filename:
186 if os.path.exists(filename):
187 io.loadfile(filename)
188 else:
189 io.set_filename(filename)
190
191 self.saved_change_hook()
192
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000193 self.load_extensions()
194
195 menu = self.menudict.get('windows')
196 if menu:
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000197 end = menu.index("end")
198 if end is None:
199 end = -1
200 if end >= 0:
201 menu.add_separator()
202 end = end + 1
203 self.wmenu_end = end
Guido van Rossumc4f752f1999-02-17 17:20:50 +0000204 WindowList.register_callback(self.postwindowsmenu)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000205
Guido van Rossumd395aee1999-06-02 11:04:29 +0000206 # Some abstractions so IDLE extensions are cross-IDE
207 self.askyesno = tkMessageBox.askyesno
208 self.askinteger = tkSimpleDialog.askinteger
209 self.showerror = tkMessageBox.showerror
210
Guido van Rossumdef2c961999-05-21 04:38:27 +0000211 if self.extensions.has_key('AutoIndent'):
212 self.extensions['AutoIndent'].set_indentation_params(
213 self.ispythonsource(filename))
Guido van Rossumec73dc62000-02-15 18:05:15 +0000214 self.set_status_bar()
215
216 def set_status_bar(self):
217 self.status_bar = self.MultiStatusBar(self.text_frame)
218 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
219 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
220 self.status_bar.pack(side=BOTTOM, fill=X)
221 self.text.bind('<KeyRelease>', self.set_line_and_column)
222 self.text.bind('<ButtonRelease>', self.set_line_and_column)
223 self.text.after_idle(self.set_line_and_column)
224
225 def set_line_and_column(self, event=None):
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000226 line, column = self.text.index(INSERT).split('.')
Guido van Rossumec73dc62000-02-15 18:05:15 +0000227 self.status_bar.set_label('column', 'Col: %s' % column)
228 self.status_bar.set_label('line', 'Ln: %s' % line)
Guido van Rossumdef2c961999-05-21 04:38:27 +0000229
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000230 def wakeup(self):
Guido van Rossum36911a11999-01-18 15:18:57 +0000231 if self.top.wm_state() == "iconic":
232 self.top.wm_deiconify()
233 else:
234 self.top.tkraise()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000235 self.text.focus_set()
236
Guido van Rossume7b2e651998-10-12 23:56:08 +0000237 menu_specs = [
Guido van Rossumb5eed031998-11-27 03:19:07 +0000238 ("file", "_File"),
239 ("edit", "_Edit"),
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000240 ("windows", "_Windows"),
Guido van Rossumb5eed031998-11-27 03:19:07 +0000241 ("help", "_Help"),
Guido van Rossume7b2e651998-10-12 23:56:08 +0000242 ]
243
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000244 def createmenubar(self):
245 mbar = self.menubar
Guido van Rossum07ec8961999-01-28 22:02:47 +0000246 self.menudict = menudict = {}
Guido van Rossume7b2e651998-10-12 23:56:08 +0000247 for name, label in self.menu_specs:
Guido van Rossum07ec8961999-01-28 22:02:47 +0000248 underline, label = prepstr(label)
249 menudict[name] = menu = Menu(mbar, name=name)
Guido van Rossumb5eed031998-11-27 03:19:07 +0000250 mbar.add_cascade(label=label, menu=menu, underline=underline)
Guido van Rossum07ec8961999-01-28 22:02:47 +0000251 self.fill_menus()
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000252
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000253 def postwindowsmenu(self):
254 # Only called when Windows menu exists
Guido van Rossum85a36a51999-06-10 17:43:17 +0000255 # XXX Actually, this Just-In-Time updating interferes badly
256 # XXX with the tear-off feature. It would be better to update
257 # XXX all Windows menus whenever the list of windows changes.
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000258 menu = self.menudict['windows']
259 end = menu.index("end")
260 if end is None:
261 end = -1
262 if end > self.wmenu_end:
263 menu.delete(self.wmenu_end+1, end)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000264 WindowList.add_windows_to_menu(menu)
265
266 rmenu = None
267
268 def right_menu_event(self, event):
269 self.text.tag_remove("sel", "1.0", "end")
270 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
271 if not self.rmenu:
272 self.make_rmenu()
273 rmenu = self.rmenu
274 self.event = event
275 iswin = sys.platform[:3] == 'win'
276 if iswin:
277 self.text.config(cursor="arrow")
278 rmenu.tk_popup(event.x_root, event.y_root)
279 if iswin:
280 self.text.config(cursor="ibeam")
281
282 rmenu_specs = [
283 # ("Label", "<<virtual-event>>"), ...
284 ("Close", "<<close-window>>"), # Example
285 ]
286
287 def make_rmenu(self):
288 rmenu = Menu(self.text, tearoff=0)
289 for label, eventname in self.rmenu_specs:
290 def command(text=self.text, eventname=eventname):
291 text.event_generate(eventname)
292 rmenu.add_command(label=label, command=command)
293 self.rmenu = rmenu
294
Guido van Rossume7b2e651998-10-12 23:56:08 +0000295 def about_dialog(self, event=None):
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000296 tkMessageBox.showinfo(self.about_title, self.about_text,
297 master=self.text)
298
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000299 helpfile = "help.txt"
300
Guido van Rossume7b2e651998-10-12 23:56:08 +0000301 def help_dialog(self, event=None):
Guido van Rossum416b9611999-08-26 23:06:05 +0000302 try:
303 helpfile = os.path.join(os.path.dirname(__file__), self.helpfile)
304 except NameError:
305 helpfile = self.helpfile
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000306 if self.flist:
307 self.flist.open(helpfile)
308 else:
309 self.io.loadfile(helpfile)
310
Guido van Rossum416b9611999-08-26 23:06:05 +0000311 help_url = "http://www.python.org/doc/current/"
Fred Drake8638ace2000-05-10 16:50:07 +0000312 if sys.platform[:3] == "win":
Tim Petersf58a7aa2000-09-22 10:05:54 +0000313 fn = os.path.dirname(__file__)
Martin v. Löwisdcd2dc22002-04-14 10:30:51 +0000314 fn = os.path.join(fn, os.pardir, os.pardir, "pythlp.chm")
Tim Petersf58a7aa2000-09-22 10:05:54 +0000315 fn = os.path.normpath(fn)
Fred Drake8638ace2000-05-10 16:50:07 +0000316 if os.path.isfile(fn):
317 help_url = fn
Martin v. Löwisdcd2dc22002-04-14 10:30:51 +0000318 else:
319 fn = os.path.dirname(__file__)
320 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
321 fn = os.path.normpath(fn)
322 if os.path.isfile(fn):
323 help_url = fn
Fred Drake8638ace2000-05-10 16:50:07 +0000324 del fn
Guido van Rossum416b9611999-08-26 23:06:05 +0000325
Martin v. Löwisdcd2dc22002-04-14 10:30:51 +0000326 def python_docs(self, event=None):
327 os.startfile(self.help_url)
328 else:
329 def python_docs(self, event=None):
330 webbrowser.open(self.help_url)
Guido van Rossum416b9611999-08-26 23:06:05 +0000331
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000332 def select_all(self, event=None):
333 self.text.tag_add("sel", "1.0", "end-1c")
334 self.text.mark_set("insert", "1.0")
335 self.text.see("insert")
336 return "break"
337
338 def remove_selection(self, event=None):
339 self.text.tag_remove("sel", "1.0", "end")
340 self.text.see("insert")
341
Guido van Rossumb3418881998-10-13 03:45:15 +0000342 def open_module(self, event=None):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000343 # XXX Shouldn't this be in IOBinding or in FileList?
Guido van Rossumb3418881998-10-13 03:45:15 +0000344 try:
345 name = self.text.get("sel.first", "sel.last")
346 except TclError:
347 name = ""
348 else:
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000349 name = name.strip()
Guido van Rossumb3418881998-10-13 03:45:15 +0000350 if not name:
351 name = tkSimpleDialog.askstring("Module",
Guido van Rossume1dedc01998-10-16 16:09:57 +0000352 "Enter the name of a Python module\n"
353 "to search on sys.path and open:",
Guido van Rossumb3418881998-10-13 03:45:15 +0000354 parent=self.text)
355 if name:
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000356 name = name.strip()
Guido van Rossumb3418881998-10-13 03:45:15 +0000357 if not name:
358 return
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000359 # XXX Ought to insert current file's directory in front of path
Guido van Rossumb3418881998-10-13 03:45:15 +0000360 try:
Raymond Hettingerb2c729f2002-09-08 03:42:01 +0000361 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossum74311b21999-06-01 18:27:14 +0000362 except (NameError, ImportError), msg:
Guido van Rossumb3418881998-10-13 03:45:15 +0000363 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
364 return
365 if type != imp.PY_SOURCE:
366 tkMessageBox.showerror("Unsupported type",
367 "%s is not a source module" % name, parent=self.text)
368 return
369 if f:
370 f.close()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000371 if self.flist:
372 self.flist.open(file)
373 else:
374 self.io.loadfile(file)
375
376 def open_class_browser(self, event=None):
377 filename = self.io.filename
378 if not filename:
379 tkMessageBox.showerror(
380 "No filename",
381 "This buffer has no associated filename",
382 master=self.text)
Guido van Rossum74311b21999-06-01 18:27:14 +0000383 self.text.focus_set()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000384 return None
385 head, tail = os.path.split(filename)
386 base, ext = os.path.splitext(tail)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000387 import ClassBrowser
388 ClassBrowser.ClassBrowser(self.flist, base, [head])
Guido van Rossumdef2c961999-05-21 04:38:27 +0000389
Guido van Rossumd6e87131999-03-10 05:18:02 +0000390 def open_path_browser(self, event=None):
391 import PathBrowser
392 PathBrowser.PathBrowser(self.flist)
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000393
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000394 def gotoline(self, lineno):
395 if lineno is not None and lineno > 0:
396 self.text.mark_set("insert", "%d.0" % lineno)
397 self.text.tag_remove("sel", "1.0", "end")
398 self.text.tag_add("sel", "insert", "insert +1l")
399 self.center()
400
401 def ispythonsource(self, filename):
402 if not filename:
Tim Petersbc0e9102002-04-04 22:55:58 +0000403 return True
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000404 base, ext = os.path.splitext(os.path.basename(filename))
405 if os.path.normcase(ext) in (".py", ".pyw"):
Tim Petersbc0e9102002-04-04 22:55:58 +0000406 return True
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000407 try:
408 f = open(filename)
409 line = f.readline()
410 f.close()
411 except IOError:
Tim Petersbc0e9102002-04-04 22:55:58 +0000412 return False
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000413 return line.startswith('#!') and 'python' in line
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000414
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000415 def close_hook(self):
416 if self.flist:
417 self.flist.close_edit(self)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000418
419 def set_close_hook(self, close_hook):
420 self.close_hook = close_hook
421
422 def filename_change_hook(self):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000423 if self.flist:
424 self.flist.filename_changed_edit(self)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000425 self.saved_change_hook()
426 if self.ispythonsource(self.io.filename):
427 self.addcolorizer()
428 else:
429 self.rmcolorizer()
430
431 def addcolorizer(self):
432 if self.color:
433 return
434 ##print "Add colorizer"
435 self.per.removefilter(self.undo)
436 self.color = self.ColorDelegator()
437 self.per.insertfilter(self.color)
438 self.per.insertfilter(self.undo)
439
440 def rmcolorizer(self):
441 if not self.color:
442 return
443 ##print "Remove colorizer"
444 self.per.removefilter(self.undo)
445 self.per.removefilter(self.color)
446 self.color = None
447 self.per.insertfilter(self.undo)
448
449 def saved_change_hook(self):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000450 short = self.short_title()
451 long = self.long_title()
452 if short and long:
453 title = short + " - " + long
454 elif short:
455 title = short
456 elif long:
457 title = long
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000458 else:
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000459 title = "Untitled"
460 icon = short or long or title
461 if not self.get_saved():
462 title = "*%s*" % title
463 icon = "*%s" % icon
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000464 self.top.wm_title(title)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000465 self.top.wm_iconname(icon)
466
467 def get_saved(self):
468 return self.undo.get_saved()
469
470 def set_saved(self, flag):
471 self.undo.set_saved(flag)
472
473 def reset_undo(self):
474 self.undo.reset_undo()
475
476 def short_title(self):
477 filename = self.io.filename
478 if filename:
479 filename = os.path.basename(filename)
480 return filename
481
482 def long_title(self):
483 return self.io.filename or ""
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000484
485 def center_insert_event(self, event):
486 self.center()
487
488 def center(self, mark="insert"):
Guido van Rossum245ddc41999-01-11 14:51:32 +0000489 text = self.text
490 top, bot = self.getwindowlines()
491 lineno = self.getlineno(mark)
492 height = bot - top
Guido van Rossum64e9d612002-01-23 15:15:13 +0000493 newtop = max(1, lineno - height//2)
Guido van Rossum245ddc41999-01-11 14:51:32 +0000494 text.yview(float(newtop))
495
496 def getwindowlines(self):
497 text = self.text
498 top = self.getlineno("@0,0")
499 bot = self.getlineno("@0,65535")
Guido van Rossum5051f4f1999-01-12 22:09:57 +0000500 if top == bot and text.winfo_height() == 1:
501 # Geometry manager hasn't run yet
Guido van Rossum245ddc41999-01-11 14:51:32 +0000502 height = int(text['height'])
503 bot = top + height - 1
504 return top, bot
505
506 def getlineno(self, mark="insert"):
507 text = self.text
508 return int(float(text.index(mark)))
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000509
510 def close_event(self, event):
511 self.close()
512
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000513 def maybesave(self):
514 if self.io:
515 return self.io.maybesave()
516
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000517 def close(self):
518 self.top.wm_deiconify()
519 self.top.tkraise()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000520 reply = self.maybesave()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000521 if reply != "cancel":
Guido van Rossum205afb41999-06-25 16:06:29 +0000522 self._close()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000523 return reply
524
Guido van Rossum205afb41999-06-25 16:06:29 +0000525 def _close(self):
526 WindowList.unregister_callback(self.postwindowsmenu)
527 if self.close_hook:
528 self.close_hook()
529 self.flist = None
530 colorizing = 0
531 self.unload_extensions()
532 self.io.close(); self.io = None
533 self.undo = None # XXX
534 if self.color:
535 colorizing = self.color.colorizing
536 doh = colorizing and self.top
537 self.color.close(doh) # Cancel colorization
538 self.text = None
539 self.vars = None
540 self.per.close(); self.per = None
541 if not colorizing:
542 self.top.destroy()
543
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000544 def load_extensions(self):
545 self.extensions = {}
546 self.load_standard_extensions()
547
Guido van Rossum205afb41999-06-25 16:06:29 +0000548 def unload_extensions(self):
549 for ins in self.extensions.values():
550 if hasattr(ins, "close"):
551 ins.close()
552 self.extensions = {}
553
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000554 def load_standard_extensions(self):
555 for name in self.get_standard_extension_names():
556 try:
557 self.load_extension(name)
558 except:
559 print "Failed to load extension", `name`
560 import traceback
561 traceback.print_exc()
562
563 def get_standard_extension_names(self):
Jeremy Hyltonae1f3bd2000-03-07 17:56:27 +0000564 return idleconf.getextensions()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000565
566 def load_extension(self, name):
Guido van Rossum9dd52091999-04-23 14:01:25 +0000567 mod = __import__(name, globals(), locals(), [])
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000568 cls = getattr(mod, name)
569 ins = cls(self)
570 self.extensions[name] = ins
571 kdnames = ["keydefs"]
572 if sys.platform == 'win32':
573 kdnames.append("windows_keydefs")
574 elif sys.platform == 'mac':
575 kdnames.append("mac_keydefs")
576 else:
577 kdnames.append("unix_keydefs")
578 keydefs = {}
579 for kdname in kdnames:
580 if hasattr(ins, kdname):
581 keydefs.update(getattr(ins, kdname))
582 if keydefs:
Guido van Rossum07ec8961999-01-28 22:02:47 +0000583 self.apply_bindings(keydefs)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000584 for vevent in keydefs.keys():
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000585 methodname = vevent.replace("-", "_")
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000586 while methodname[:1] == '<':
587 methodname = methodname[1:]
588 while methodname[-1:] == '>':
589 methodname = methodname[:-1]
590 methodname = methodname + "_event"
591 if hasattr(ins, methodname):
592 self.text.bind(vevent, getattr(ins, methodname))
593 if hasattr(ins, "menudefs"):
Guido van Rossum07ec8961999-01-28 22:02:47 +0000594 self.fill_menus(ins.menudefs, keydefs)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000595 return ins
596
Guido van Rossum07ec8961999-01-28 22:02:47 +0000597 def apply_bindings(self, keydefs=None):
598 if keydefs is None:
599 keydefs = self.Bindings.default_keydefs
600 text = self.text
601 text.keydefs = keydefs
602 for event, keylist in keydefs.items():
603 if keylist:
604 apply(text.event_add, (event,) + tuple(keylist))
605
606 def fill_menus(self, defs=None, keydefs=None):
Guido van Rossum85a36a51999-06-10 17:43:17 +0000607 # Fill the menus. Menus that are absent or None in
608 # self.menudict are ignored.
Guido van Rossum07ec8961999-01-28 22:02:47 +0000609 if defs is None:
610 defs = self.Bindings.menudefs
611 if keydefs is None:
612 keydefs = self.Bindings.default_keydefs
613 menudict = self.menudict
614 text = self.text
615 for mname, itemlist in defs:
616 menu = menudict.get(mname)
617 if not menu:
618 continue
619 for item in itemlist:
620 if not item:
621 menu.add_separator()
622 else:
623 label, event = item
624 checkbutton = (label[:1] == '!')
625 if checkbutton:
626 label = label[1:]
627 underline, label = prepstr(label)
628 accelerator = get_accelerator(keydefs, event)
629 def command(text=text, event=event):
630 text.event_generate(event)
631 if checkbutton:
632 var = self.getrawvar(event, BooleanVar)
633 menu.add_checkbutton(label=label, underline=underline,
634 command=command, accelerator=accelerator,
635 variable=var)
636 else:
637 menu.add_command(label=label, underline=underline,
638 command=command, accelerator=accelerator)
Guido van Rossumdef2c961999-05-21 04:38:27 +0000639
Guido van Rossum07ec8961999-01-28 22:02:47 +0000640 def getvar(self, name):
641 var = self.getrawvar(name)
642 if var:
643 return var.get()
Guido van Rossumdef2c961999-05-21 04:38:27 +0000644
Guido van Rossum07ec8961999-01-28 22:02:47 +0000645 def setvar(self, name, value, vartype=None):
646 var = self.getrawvar(name, vartype)
647 if var:
648 var.set(value)
Guido van Rossumdef2c961999-05-21 04:38:27 +0000649
Guido van Rossum07ec8961999-01-28 22:02:47 +0000650 def getrawvar(self, name, vartype=None):
Guido van Rossumb7ebb831999-01-28 22:24:30 +0000651 var = self.vars.get(name)
652 if not var and vartype:
653 self.vars[name] = var = vartype(self.text)
654 return var
Guido van Rossum07ec8961999-01-28 22:02:47 +0000655
Guido van Rossumf4a15081999-06-03 14:32:16 +0000656 # Tk implementations of "virtual text methods" -- each platform
657 # reusing IDLE's support code needs to define these for its GUI's
658 # flavor of widget.
659
660 # Is character at text_index in a Python string? Return 0 for
Guido van Rossum85a36a51999-06-10 17:43:17 +0000661 # "guaranteed no", true for anything else. This info is expensive
662 # to compute ab initio, but is probably already known by the
663 # platform's colorizer.
Guido van Rossumf4a15081999-06-03 14:32:16 +0000664
665 def is_char_in_string(self, text_index):
666 if self.color:
Guido van Rossum85a36a51999-06-10 17:43:17 +0000667 # Return true iff colorizer hasn't (re)gotten this far
668 # yet, or the character is tagged as being in a string
Guido van Rossumf4a15081999-06-03 14:32:16 +0000669 return self.text.tag_prevrange("TODO", text_index) or \
670 "STRING" in self.text.tag_names(text_index)
671 else:
Guido van Rossum85a36a51999-06-10 17:43:17 +0000672 # The colorizer is missing: assume the worst
Guido van Rossumf4a15081999-06-03 14:32:16 +0000673 return 1
674
Guido van Rossum85a36a51999-06-10 17:43:17 +0000675 # If a selection is defined in the text widget, return (start,
676 # end) as Tkinter text indices, otherwise return (None, None)
Guido van Rossum13205601999-06-11 15:03:00 +0000677 def get_selection_indices(self):
Guido van Rossum85a36a51999-06-10 17:43:17 +0000678 try:
679 first = self.text.index("sel.first")
680 last = self.text.index("sel.last")
681 return first, last
682 except TclError:
683 return None, None
684
Guido van Rossum13205601999-06-11 15:03:00 +0000685 # Return the text widget's current view of what a tab stop means
686 # (equivalent width in spaces).
687
688 def get_tabwidth(self):
689 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
690 return int(current)
691
692 # Set the text widget's current view of what a tab stop means.
693
694 def set_tabwidth(self, newtabwidth):
695 text = self.text
696 if self.get_tabwidth() != newtabwidth:
697 pixels = text.tk.call("font", "measure", text["font"],
698 "-displayof", text.master,
Guido van Rossumbdd90172000-09-20 00:17:39 +0000699 "n" * newtabwidth)
Guido van Rossum13205601999-06-11 15:03:00 +0000700 text.configure(tabs=pixels)
701
Guido van Rossum07ec8961999-01-28 22:02:47 +0000702def prepstr(s):
Guido van Rossum85a36a51999-06-10 17:43:17 +0000703 # Helper to extract the underscore from a string, e.g.
704 # prepstr("Co_py") returns (2, "Copy").
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000705 i = s.find('_')
Guido van Rossum07ec8961999-01-28 22:02:47 +0000706 if i >= 0:
707 s = s[:i] + s[i+1:]
708 return i, s
709
710
711keynames = {
712 'bracketleft': '[',
713 'bracketright': ']',
714 'slash': '/',
715}
716
717def get_accelerator(keydefs, event):
718 keylist = keydefs.get(event)
719 if not keylist:
720 return ""
721 s = keylist[0]
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000722 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
Guido van Rossum07ec8961999-01-28 22:02:47 +0000723 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
724 s = re.sub("Key-", "", s)
725 s = re.sub("Control-", "Ctrl-", s)
726 s = re.sub("-", "+", s)
727 s = re.sub("><", " ", s)
728 s = re.sub("<", "", s)
729 s = re.sub(">", "", s)
730 return s
731
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000732
733def fixwordbreaks(root):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000734 # Make sure that Tk's double-click and next/previous word
735 # operations use our definition of a word (i.e. an identifier)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000736 tk = root.tk
737 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
738 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
739 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
740
741
742def test():
743 root = Tk()
744 fixwordbreaks(root)
745 root.withdraw()
746 if sys.argv[1:]:
747 filename = sys.argv[1]
748 else:
749 filename = None
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000750 edit = EditorWindow(root=root, filename=filename)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000751 edit.set_close_hook(root.quit)
752 root.mainloop()
753 root.destroy()
754
755if __name__ == '__main__':
756 test()