blob: 96a56de494c7954a40c555858560b594fa4bfce2 [file] [log] [blame]
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +00001import sys
2import os
3import string
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
Guido van Rossum504b0bf1999-01-02 21:28:54 +00008import idlever
9
10# File menu
11
12#$ event <<open-module>>
13#$ win <Alt-m>
14#$ unix <Control-x><Control-m>
15
16#$ event <<open-class-browser>>
17#$ win <Alt-c>
18#$ unix <Control-x><Control-b>
19
20#$ event <<close-window>>
21#$ unix <Control-x><Control-0>
22#$ unix <Control-x><Key-0>
23#$ win <Alt-F4>
24
25# Edit menu
26
27#$ event <<Copy>>
28#$ win <Control-c>
29#$ unix <Alt-w>
30
31#$ event <<Cut>>
32#$ win <Control-x>
33#$ unix <Control-w>
34
35#$ event <<Paste>>
36#$ win <Control-v>
37#$ unix <Control-y>
38
39#$ event <<select-all>>
40#$ win <Alt-a>
41#$ unix <Alt-a>
42
43# Help menu
44
45#$ event <<help>>
46#$ win <F1>
47#$ unix <F1>
48
49#$ event <<about-idle>>
50
51# Events without menu entries
52
53#$ event <<remove-selection>>
54#$ win <Escape>
55
56#$ event <<center-insert>>
57#$ win <Control-l>
58#$ unix <Control-l>
59
60#$ event <<do-nothing>>
61#$ unix <Control-x>
62
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000063
Guido van Rossum2aeeb551998-10-12 21:01:37 +000064about_title = "About IDLE"
65about_text = """\
Guido van Rossum504b0bf1999-01-02 21:28:54 +000066IDLE %s
Guido van Rossum2aeeb551998-10-12 21:01:37 +000067
Guido van Rossum504b0bf1999-01-02 21:28:54 +000068An Integrated DeveLopment Environment for Python
Guido van Rossum2aeeb551998-10-12 21:01:37 +000069
70by Guido van Rossum
Guido van Rossum504b0bf1999-01-02 21:28:54 +000071""" % idlever.IDLE_VERSION
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000072
73class EditorWindow:
74
75 from Percolator import Percolator
76 from ColorDelegator import ColorDelegator
77 from UndoDelegator import UndoDelegator
78 from IOBinding import IOBinding
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000079 import Bindings
Guido van Rossum504b0bf1999-01-02 21:28:54 +000080 from Tkinter import Toplevel
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000081
Guido van Rossum504b0bf1999-01-02 21:28:54 +000082 about_title = about_title
83 about_text = about_text
84
85 def __init__(self, flist=None, filename=None, key=None, root=None):
86 self.flist = flist
87 root = root or flist.root
Guido van Rossum2aeeb551998-10-12 21:01:37 +000088 self.root = root
89 self.menubar = Menu(root)
Guido van Rossum504b0bf1999-01-02 21:28:54 +000090 self.top = top = self.Toplevel(root, menu=self.menubar)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000091 self.vbar = vbar = Scrollbar(top, name='vbar')
Guido van Rossum504b0bf1999-01-02 21:28:54 +000092 self.text = text = Text(top, name='text', padx=5,
93 background="white", wrap="none")
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000094
Guido van Rossum2aeeb551998-10-12 21:01:37 +000095 self.createmenubar()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000096 self.Bindings.apply_bindings(text)
97
98 self.top.protocol("WM_DELETE_WINDOW", self.close)
99 self.top.bind("<<close-window>>", self.close_event)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000100 text.bind("<<center-insert>>", self.center_insert_event)
101 text.bind("<<help>>", self.help_dialog)
102 text.bind("<<about-idle>>", self.about_dialog)
103 text.bind("<<open-module>>", self.open_module)
104 text.bind("<<do-nothing>>", lambda event: "break")
105 text.bind("<<select-all>>", self.select_all)
106 text.bind("<<remove-selection>>", self.remove_selection)
107 text.bind("<3>", self.right_menu_event)
108 if flist:
109 flist.inversedict[self] = key
110 if key:
111 flist.dict[key] = self
112 text.bind("<<open-new-window>>", self.flist.new_callback)
113 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
114 text.bind("<<open-class-browser>>", self.open_class_browser)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000115
116 vbar['command'] = text.yview
117 vbar.pack(side=RIGHT, fill=Y)
118
119 text['yscrollcommand'] = vbar.set
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000120 if sys.platform[:3] == 'win':
121 text['font'] = ("lucida console", 8)
122 text.pack(side=LEFT, fill=BOTH, expand=1)
123 text.focus_set()
124
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000125 self.per = per = self.Percolator(text)
126 if self.ispythonsource(filename):
127 self.color = color = self.ColorDelegator(); per.insertfilter(color)
128 ##print "Initial colorizer"
129 else:
130 ##print "No initial colorizer"
131 self.color = None
132 self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000133 self.io = io = self.IOBinding(self)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000134
135 undo.set_saved_change_hook(self.saved_change_hook)
136 io.set_filename_change_hook(self.filename_change_hook)
137
138 if filename:
139 if os.path.exists(filename):
140 io.loadfile(filename)
141 else:
142 io.set_filename(filename)
143
144 self.saved_change_hook()
145
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000146 self.load_extensions()
147
148 menu = self.menudict.get('windows')
149 if menu:
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000150 end = menu.index("end")
151 if end is None:
152 end = -1
153 if end >= 0:
154 menu.add_separator()
155 end = end + 1
156 self.wmenu_end = end
157 menu.configure(postcommand=self.postwindowsmenu)
158
159 def wakeup(self):
Guido van Rossum36911a11999-01-18 15:18:57 +0000160 if self.top.wm_state() == "iconic":
161 self.top.wm_deiconify()
162 else:
163 self.top.tkraise()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000164 self.text.focus_set()
165
Guido van Rossume7b2e651998-10-12 23:56:08 +0000166 menu_specs = [
Guido van Rossumb5eed031998-11-27 03:19:07 +0000167 ("file", "_File"),
168 ("edit", "_Edit"),
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000169 ("windows", "_Windows"),
Guido van Rossumb5eed031998-11-27 03:19:07 +0000170 ("help", "_Help"),
Guido van Rossume7b2e651998-10-12 23:56:08 +0000171 ]
172
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000173 def createmenubar(self):
174 mbar = self.menubar
Guido van Rossume7b2e651998-10-12 23:56:08 +0000175 self.menudict = mdict = {}
176 for name, label in self.menu_specs:
Guido van Rossumb5eed031998-11-27 03:19:07 +0000177 underline, label = self.Bindings.prepstr(label)
Guido van Rossume7b2e651998-10-12 23:56:08 +0000178 mdict[name] = menu = Menu(mbar, name=name)
Guido van Rossumb5eed031998-11-27 03:19:07 +0000179 mbar.add_cascade(label=label, menu=menu, underline=underline)
Guido van Rossume7b2e651998-10-12 23:56:08 +0000180 self.Bindings.fill_menus(self.text, mdict)
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000181
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000182 def postwindowsmenu(self):
183 # Only called when Windows menu exists
184 menu = self.menudict['windows']
185 end = menu.index("end")
186 if end is None:
187 end = -1
188 if end > self.wmenu_end:
189 menu.delete(self.wmenu_end+1, end)
190 import WindowList
191 WindowList.add_windows_to_menu(menu)
192
193 rmenu = None
194
195 def right_menu_event(self, event):
196 self.text.tag_remove("sel", "1.0", "end")
197 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
198 if not self.rmenu:
199 self.make_rmenu()
200 rmenu = self.rmenu
201 self.event = event
202 iswin = sys.platform[:3] == 'win'
203 if iswin:
204 self.text.config(cursor="arrow")
205 rmenu.tk_popup(event.x_root, event.y_root)
206 if iswin:
207 self.text.config(cursor="ibeam")
208
209 rmenu_specs = [
210 # ("Label", "<<virtual-event>>"), ...
211 ("Close", "<<close-window>>"), # Example
212 ]
213
214 def make_rmenu(self):
215 rmenu = Menu(self.text, tearoff=0)
216 for label, eventname in self.rmenu_specs:
217 def command(text=self.text, eventname=eventname):
218 text.event_generate(eventname)
219 rmenu.add_command(label=label, command=command)
220 self.rmenu = rmenu
221
Guido van Rossume7b2e651998-10-12 23:56:08 +0000222 def about_dialog(self, event=None):
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000223 tkMessageBox.showinfo(self.about_title, self.about_text,
224 master=self.text)
225
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000226 helpfile = "help.txt"
227
Guido van Rossume7b2e651998-10-12 23:56:08 +0000228 def help_dialog(self, event=None):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000229 helpfile = self.helpfile
230 if not os.path.exists(helpfile):
231 base = os.path.basename(self.helpfile)
232 for dir in sys.path:
233 fullname = os.path.join(dir, base)
234 if os.path.exists(fullname):
235 helpfile = fullname
236 break
237 if self.flist:
238 self.flist.open(helpfile)
239 else:
240 self.io.loadfile(helpfile)
241
242 def select_all(self, event=None):
243 self.text.tag_add("sel", "1.0", "end-1c")
244 self.text.mark_set("insert", "1.0")
245 self.text.see("insert")
246 return "break"
247
248 def remove_selection(self, event=None):
249 self.text.tag_remove("sel", "1.0", "end")
250 self.text.see("insert")
251
Guido van Rossumb3418881998-10-13 03:45:15 +0000252 def open_module(self, event=None):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000253 # XXX Shouldn't this be in IOBinding or in FileList?
Guido van Rossumb3418881998-10-13 03:45:15 +0000254 try:
255 name = self.text.get("sel.first", "sel.last")
256 except TclError:
257 name = ""
258 else:
259 name = string.strip(name)
260 if not name:
261 name = tkSimpleDialog.askstring("Module",
Guido van Rossume1dedc01998-10-16 16:09:57 +0000262 "Enter the name of a Python module\n"
263 "to search on sys.path and open:",
Guido van Rossumb3418881998-10-13 03:45:15 +0000264 parent=self.text)
265 if name:
266 name = string.strip(name)
267 if not name:
268 return
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000269 # XXX Ought to support package syntax
270 # XXX Ought to insert current file's directory in front of path
Guido van Rossumb3418881998-10-13 03:45:15 +0000271 try:
272 (f, file, (suffix, mode, type)) = imp.find_module(name)
273 except ImportError, msg:
274 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
275 return
276 if type != imp.PY_SOURCE:
277 tkMessageBox.showerror("Unsupported type",
278 "%s is not a source module" % name, parent=self.text)
279 return
280 if f:
281 f.close()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000282 if self.flist:
283 self.flist.open(file)
284 else:
285 self.io.loadfile(file)
286
287 def open_class_browser(self, event=None):
288 filename = self.io.filename
289 if not filename:
290 tkMessageBox.showerror(
291 "No filename",
292 "This buffer has no associated filename",
293 master=self.text)
294 return None
295 head, tail = os.path.split(filename)
296 base, ext = os.path.splitext(tail)
297 import pyclbr
298 if pyclbr._modules.has_key(base):
299 del pyclbr._modules[base]
Guido van Rossum245ddc41999-01-11 14:51:32 +0000300 save_cursor = self.text["cursor"]
301 self.text["cursor"] = "watch"
302 self.text.update_idletasks()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000303 import ClassBrowser
304 ClassBrowser.ClassBrowser(self.flist, base, [head])
Guido van Rossum245ddc41999-01-11 14:51:32 +0000305 self.text["cursor"] = save_cursor
Guido van Rossum2aeeb551998-10-12 21:01:37 +0000306
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000307 def gotoline(self, lineno):
308 if lineno is not None and lineno > 0:
309 self.text.mark_set("insert", "%d.0" % lineno)
310 self.text.tag_remove("sel", "1.0", "end")
311 self.text.tag_add("sel", "insert", "insert +1l")
312 self.center()
313
314 def ispythonsource(self, filename):
315 if not filename:
316 return 1
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000317 base, ext = os.path.splitext(os.path.basename(filename))
318 if os.path.normcase(ext) in (".py", ".pyw"):
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000319 return 1
320 try:
321 f = open(filename)
322 line = f.readline()
323 f.close()
324 except IOError:
325 return 0
326 return line[:2] == '#!' and string.find(line, 'python') >= 0
327
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000328 def close_hook(self):
329 if self.flist:
330 self.flist.close_edit(self)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000331
332 def set_close_hook(self, close_hook):
333 self.close_hook = close_hook
334
335 def filename_change_hook(self):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000336 if self.flist:
337 self.flist.filename_changed_edit(self)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000338 self.saved_change_hook()
339 if self.ispythonsource(self.io.filename):
340 self.addcolorizer()
341 else:
342 self.rmcolorizer()
343
344 def addcolorizer(self):
345 if self.color:
346 return
347 ##print "Add colorizer"
348 self.per.removefilter(self.undo)
349 self.color = self.ColorDelegator()
350 self.per.insertfilter(self.color)
351 self.per.insertfilter(self.undo)
352
353 def rmcolorizer(self):
354 if not self.color:
355 return
356 ##print "Remove colorizer"
357 self.per.removefilter(self.undo)
358 self.per.removefilter(self.color)
359 self.color = None
360 self.per.insertfilter(self.undo)
361
362 def saved_change_hook(self):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000363 short = self.short_title()
364 long = self.long_title()
365 if short and long:
366 title = short + " - " + long
367 elif short:
368 title = short
369 elif long:
370 title = long
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000371 else:
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000372 title = "Untitled"
373 icon = short or long or title
374 if not self.get_saved():
375 title = "*%s*" % title
376 icon = "*%s" % icon
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000377 self.top.wm_title(title)
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000378 self.top.wm_iconname(icon)
379
380 def get_saved(self):
381 return self.undo.get_saved()
382
383 def set_saved(self, flag):
384 self.undo.set_saved(flag)
385
386 def reset_undo(self):
387 self.undo.reset_undo()
388
389 def short_title(self):
390 filename = self.io.filename
391 if filename:
392 filename = os.path.basename(filename)
393 return filename
394
395 def long_title(self):
396 return self.io.filename or ""
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000397
398 def center_insert_event(self, event):
399 self.center()
400
401 def center(self, mark="insert"):
Guido van Rossum245ddc41999-01-11 14:51:32 +0000402 text = self.text
403 top, bot = self.getwindowlines()
404 lineno = self.getlineno(mark)
405 height = bot - top
406 newtop = max(1, lineno - height/2)
407 text.yview(float(newtop))
408
409 def getwindowlines(self):
410 text = self.text
411 top = self.getlineno("@0,0")
412 bot = self.getlineno("@0,65535")
Guido van Rossum5051f4f1999-01-12 22:09:57 +0000413 if top == bot and text.winfo_height() == 1:
414 # Geometry manager hasn't run yet
Guido van Rossum245ddc41999-01-11 14:51:32 +0000415 height = int(text['height'])
416 bot = top + height - 1
417 return top, bot
418
419 def getlineno(self, mark="insert"):
420 text = self.text
421 return int(float(text.index(mark)))
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000422
423 def close_event(self, event):
424 self.close()
425
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000426 def maybesave(self):
427 if self.io:
428 return self.io.maybesave()
429
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000430 def close(self):
431 self.top.wm_deiconify()
432 self.top.tkraise()
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000433 reply = self.maybesave()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000434 if reply != "cancel":
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000435 if self.close_hook:
436 self.close_hook()
Guido van Rossum5051f4f1999-01-12 22:09:57 +0000437 colorizing = 0
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000438 if self.color:
Guido van Rossum5051f4f1999-01-12 22:09:57 +0000439 colorizing = self.color.colorizing
440 doh = colorizing and self.top
441 self.color.close(doh) # Cancel colorization
442 if not colorizing:
443 self.top.destroy()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000444 return reply
445
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000446 def load_extensions(self):
447 self.extensions = {}
448 self.load_standard_extensions()
449
450 def load_standard_extensions(self):
451 for name in self.get_standard_extension_names():
452 try:
453 self.load_extension(name)
454 except:
455 print "Failed to load extension", `name`
456 import traceback
457 traceback.print_exc()
458
459 def get_standard_extension_names(self):
460 import extend
461 return extend.standard
462
463 def load_extension(self, name):
464 mod = __import__(name)
465 cls = getattr(mod, name)
466 ins = cls(self)
467 self.extensions[name] = ins
468 kdnames = ["keydefs"]
469 if sys.platform == 'win32':
470 kdnames.append("windows_keydefs")
471 elif sys.platform == 'mac':
472 kdnames.append("mac_keydefs")
473 else:
474 kdnames.append("unix_keydefs")
475 keydefs = {}
476 for kdname in kdnames:
477 if hasattr(ins, kdname):
478 keydefs.update(getattr(ins, kdname))
479 if keydefs:
480 self.Bindings.apply_bindings(self.text, keydefs)
481 for vevent in keydefs.keys():
482 methodname = string.replace(vevent, "-", "_")
483 while methodname[:1] == '<':
484 methodname = methodname[1:]
485 while methodname[-1:] == '>':
486 methodname = methodname[:-1]
487 methodname = methodname + "_event"
488 if hasattr(ins, methodname):
489 self.text.bind(vevent, getattr(ins, methodname))
490 if hasattr(ins, "menudefs"):
491 self.Bindings.fill_menus(self.text, self. menudict,
492 ins.menudefs, keydefs)
493 return ins
494
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000495
496def fixwordbreaks(root):
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000497 # Make sure that Tk's double-click and next/previous word
498 # operations use our definition of a word (i.e. an identifier)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000499 tk = root.tk
500 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
501 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
502 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
503
504
505def test():
506 root = Tk()
507 fixwordbreaks(root)
508 root.withdraw()
509 if sys.argv[1:]:
510 filename = sys.argv[1]
511 else:
512 filename = None
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000513 edit = EditorWindow(root=root, filename=filename)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000514 edit.set_close_hook(root.quit)
515 root.mainloop()
516 root.destroy()
517
518if __name__ == '__main__':
519 test()