12 from IdleConf
import idleconf
14 # The default tab setting for a Text widget, in average-width characters.
15 TK_TABWIDTH_DEFAULT
= 8
19 #$ event <<open-module>>
21 #$ unix <Control-x><Control-m>
23 #$ event <<open-class-browser>>
25 #$ unix <Control-x><Control-b>
27 #$ event <<open-path-browser>>
29 #$ event <<close-window>>
31 #$ unix <Control-x><Control-0>
32 #$ unix <Control-x><Key-0>
49 #$ event <<select-all>>
59 #$ event <<about-idle>>
61 # Events without menu entries
63 #$ event <<remove-selection>>
66 #$ event <<center-insert>>
70 #$ event <<do-nothing>>
74 about_title
= "About IDLE"
78 An Integrated DeveLopment Environment for Python
81 """ % idlever
.IDLE_VERSION
83 def _find_module(fullname
, path
=None):
84 """Version of imp.find_module() that handles hierarchical module names"""
87 for tgt
in fullname
.split('.'):
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
)
94 path
= module
.__path
__
95 return file, filename
, descr
99 from Percolator
import Percolator
100 from ColorDelegator
import ColorDelegator
101 from UndoDelegator
import UndoDelegator
102 from IOBinding
import IOBinding
104 from Tkinter
import Toplevel
105 from MultiStatusBar
import MultiStatusBar
107 about_title
= about_title
108 about_text
= about_text
111 runnable
= False # Shell window cannot Import Module or Run Script
113 def __init__(self
, flist
=None, filename
=None, key
=None, root
=None):
114 edconf
= idleconf
.getsection('EditorWindow')
115 coconf
= idleconf
.getsection('Colors')
117 root
= root
or flist
.root
120 self
.vars = flist
.vars
121 self
.menubar
= Menu(root
)
122 self
.top
= top
= self
.Toplevel(root
, menu
=self
.menubar
)
123 self
.vbar
= vbar
= Scrollbar(top
, name
='vbar')
124 self
.text_frame
= text_frame
= Frame(top
)
125 self
.text
= text
= Text(text_frame
, name
='text', padx
=5,
126 foreground
=coconf
.getdef('normal-foreground'),
127 background
=coconf
.getdef('normal-background'),
128 highlightcolor
=coconf
.getdef('hilite-foreground'),
129 highlightbackground
=coconf
.getdef('hilite-background'),
130 insertbackground
=coconf
.getdef('cursor-background'),
131 width
=edconf
.getint('width'),
132 height
=edconf
.getint('height'),
136 self
.apply_bindings()
138 self
.top
.protocol("WM_DELETE_WINDOW", self
.close
)
139 self
.top
.bind("<<close-window>>", self
.close_event
)
140 text
.bind("<<center-insert>>", self
.center_insert_event
)
141 text
.bind("<<help>>", self
.help_dialog
)
142 text
.bind("<<python-docs>>", self
.python_docs
)
143 text
.bind("<<about-idle>>", self
.about_dialog
)
144 text
.bind("<<open-module>>", self
.open_module
)
145 text
.bind("<<do-nothing>>", lambda event
: "break")
146 text
.bind("<<select-all>>", self
.select_all
)
147 text
.bind("<<remove-selection>>", self
.remove_selection
)
148 text
.bind("<3>", self
.right_menu_event
)
150 flist
.inversedict
[self
] = key
152 flist
.dict[key
] = self
153 text
.bind("<<open-new-window>>", self
.flist
.new_callback
)
154 text
.bind("<<close-all-windows>>", self
.flist
.close_all_callback
)
155 text
.bind("<<open-class-browser>>", self
.open_class_browser
)
156 text
.bind("<<open-path-browser>>", self
.open_path_browser
)
158 vbar
['command'] = text
.yview
159 vbar
.pack(side
=RIGHT
, fill
=Y
)
161 text
['yscrollcommand'] = vbar
.set
162 text
['font'] = edconf
.get('font-name'), edconf
.get('font-size')
163 text_frame
.pack(side
=LEFT
, fill
=BOTH
, expand
=1)
164 text
.pack(side
=TOP
, fill
=BOTH
, expand
=1)
167 self
.per
= per
= self
.Percolator(text
)
168 if self
.ispythonsource(filename
):
169 self
.color
= color
= self
.ColorDelegator(); per
.insertfilter(color
)
170 ##print "Initial colorizer"
172 ##print "No initial colorizer"
174 self
.undo
= undo
= self
.UndoDelegator(); per
.insertfilter(undo
)
175 self
.io
= io
= self
.IOBinding(self
)
177 text
.undo_block_start
= undo
.undo_block_start
178 text
.undo_block_stop
= undo
.undo_block_stop
179 undo
.set_saved_change_hook(self
.saved_change_hook
)
180 io
.set_filename_change_hook(self
.filename_change_hook
)
183 if os
.path
.exists(filename
):
184 io
.loadfile(filename
)
186 io
.set_filename(filename
)
188 self
.saved_change_hook()
190 self
.load_extensions()
192 menu
= self
.menudict
.get('windows')
194 end
= menu
.index("end")
201 WindowList
.register_callback(self
.postwindowsmenu
)
203 # Some abstractions so IDLE extensions are cross-IDE
204 self
.askyesno
= tkMessageBox
.askyesno
205 self
.askinteger
= tkSimpleDialog
.askinteger
206 self
.showerror
= tkMessageBox
.showerror
208 if self
.extensions
.has_key('AutoIndent'):
209 self
.extensions
['AutoIndent'].set_indentation_params(
210 self
.ispythonsource(filename
))
211 self
.set_status_bar()
213 def set_status_bar(self
):
214 self
.status_bar
= self
.MultiStatusBar(self
.text_frame
)
215 self
.status_bar
.set_label('column', 'Col: ?', side
=RIGHT
)
216 self
.status_bar
.set_label('line', 'Ln: ?', side
=RIGHT
)
217 self
.status_bar
.pack(side
=BOTTOM
, fill
=X
)
218 self
.text
.bind('<KeyRelease>', self
.set_line_and_column
)
219 self
.text
.bind('<ButtonRelease>', self
.set_line_and_column
)
220 self
.text
.after_idle(self
.set_line_and_column
)
222 def set_line_and_column(self
, event
=None):
223 line
, column
= self
.text
.index(INSERT
).split('.')
224 self
.status_bar
.set_label('column', 'Col: %s' % column
)
225 self
.status_bar
.set_label('line', 'Ln: %s' % line
)
228 if self
.top
.wm_state() == "iconic":
229 self
.top
.wm_deiconify()
232 self
.text
.focus_set()
237 ("windows", "_Windows"),
241 def createmenubar(self
):
243 self
.menudict
= menudict
= {}
244 for name
, label
in self
.menu_specs
:
245 underline
, label
= prepstr(label
)
246 menudict
[name
] = menu
= Menu(mbar
, name
=name
)
247 mbar
.add_cascade(label
=label
, menu
=menu
, underline
=underline
)
250 def postwindowsmenu(self
):
251 # Only called when Windows menu exists
252 # XXX Actually, this Just-In-Time updating interferes badly
253 # XXX with the tear-off feature. It would be better to update
254 # XXX all Windows menus whenever the list of windows changes.
255 menu
= self
.menudict
['windows']
256 end
= menu
.index("end")
259 if end
> self
.wmenu_end
:
260 menu
.delete(self
.wmenu_end
+1, end
)
261 WindowList
.add_windows_to_menu(menu
)
265 def right_menu_event(self
, event
):
266 self
.text
.tag_remove("sel", "1.0", "end")
267 self
.text
.mark_set("insert", "@%d,%d" % (event
.x
, event
.y
))
272 iswin
= sys
.platform
[:3] == 'win'
274 self
.text
.config(cursor
="arrow")
275 rmenu
.tk_popup(event
.x_root
, event
.y_root
)
277 self
.text
.config(cursor
="ibeam")
280 # ("Label", "<<virtual-event>>"), ...
281 ("Close", "<<close-window>>"), # Example
284 def make_rmenu(self
):
285 rmenu
= Menu(self
.text
, tearoff
=0)
286 for label
, eventname
in self
.rmenu_specs
:
287 def command(text
=self
.text
, eventname
=eventname
):
288 text
.event_generate(eventname
)
289 rmenu
.add_command(label
=label
, command
=command
)
292 def about_dialog(self
, event
=None):
293 tkMessageBox
.showinfo(self
.about_title
, self
.about_text
,
296 helpfile
= "help.txt"
298 def help_dialog(self
, event
=None):
300 helpfile
= os
.path
.join(os
.path
.dirname(__file__
), self
.helpfile
)
302 helpfile
= self
.helpfile
304 self
.flist
.open(helpfile
)
306 self
.io
.loadfile(helpfile
)
308 help_url
= "http://www.python.org/doc/current/"
309 if sys
.platform
[:3] == "win":
310 fn
= os
.path
.dirname(__file__
)
311 fn
= os
.path
.join(fn
, os
.pardir
, os
.pardir
, "pythlp.chm")
312 fn
= os
.path
.normpath(fn
)
313 if os
.path
.isfile(fn
):
316 fn
= os
.path
.dirname(__file__
)
317 fn
= os
.path
.join(fn
, os
.pardir
, os
.pardir
, "Doc", "index.html")
318 fn
= os
.path
.normpath(fn
)
319 if os
.path
.isfile(fn
):
323 def python_docs(self
, event
=None):
324 os
.startfile(self
.help_url
)
326 def python_docs(self
, event
=None):
327 webbrowser
.open(self
.help_url
)
329 def select_all(self
, event
=None):
330 self
.text
.tag_add("sel", "1.0", "end-1c")
331 self
.text
.mark_set("insert", "1.0")
332 self
.text
.see("insert")
335 def remove_selection(self
, event
=None):
336 self
.text
.tag_remove("sel", "1.0", "end")
337 self
.text
.see("insert")
339 def open_module(self
, event
=None):
340 # XXX Shouldn't this be in IOBinding or in FileList?
342 name
= self
.text
.get("sel.first", "sel.last")
348 name
= tkSimpleDialog
.askstring("Module",
349 "Enter the name of a Python module\n"
350 "to search on sys.path and open:",
356 # XXX Ought to insert current file's directory in front of path
358 (f
, file, (suffix
, mode
, type)) = _find_module(name
)
359 except (NameError, ImportError), msg
:
360 tkMessageBox
.showerror("Import error", str(msg
), parent
=self
.text
)
362 if type != imp
.PY_SOURCE
:
363 tkMessageBox
.showerror("Unsupported type",
364 "%s is not a source module" % name
, parent
=self
.text
)
369 self
.flist
.open(file)
371 self
.io
.loadfile(file)
373 def open_class_browser(self
, event
=None):
374 filename
= self
.io
.filename
376 tkMessageBox
.showerror(
378 "This buffer has no associated filename",
380 self
.text
.focus_set()
382 head
, tail
= os
.path
.split(filename
)
383 base
, ext
= os
.path
.splitext(tail
)
385 ClassBrowser
.ClassBrowser(self
.flist
, base
, [head
])
387 def open_path_browser(self
, event
=None):
389 PathBrowser
.PathBrowser(self
.flist
)
391 def gotoline(self
, lineno
):
392 if lineno
is not None and lineno
> 0:
393 self
.text
.mark_set("insert", "%d.0" % lineno
)
394 self
.text
.tag_remove("sel", "1.0", "end")
395 self
.text
.tag_add("sel", "insert", "insert +1l")
398 def ispythonsource(self
, filename
):
401 base
, ext
= os
.path
.splitext(os
.path
.basename(filename
))
402 if os
.path
.normcase(ext
) in (".py", ".pyw"):
410 return line
.startswith('#!') and 'python' in line
412 def close_hook(self
):
414 self
.flist
.close_edit(self
)
416 def set_close_hook(self
, close_hook
):
417 self
.close_hook
= close_hook
419 def filename_change_hook(self
):
421 self
.flist
.filename_changed_edit(self
)
422 self
.saved_change_hook()
423 if self
.ispythonsource(self
.io
.filename
):
428 def addcolorizer(self
):
431 ##print "Add colorizer"
432 self
.per
.removefilter(self
.undo
)
433 self
.color
= self
.ColorDelegator()
434 self
.per
.insertfilter(self
.color
)
435 self
.per
.insertfilter(self
.undo
)
437 def rmcolorizer(self
):
440 ##print "Remove colorizer"
441 self
.per
.removefilter(self
.undo
)
442 self
.per
.removefilter(self
.color
)
444 self
.per
.insertfilter(self
.undo
)
446 def saved_change_hook(self
):
447 short
= self
.short_title()
448 long = self
.long_title()
450 title
= short
+ " - " + long
457 icon
= short
or long or title
458 if not self
.get_saved():
459 title
= "*%s*" % title
461 self
.top
.wm_title(title
)
462 self
.top
.wm_iconname(icon
)
465 return self
.undo
.get_saved()
467 def set_saved(self
, flag
):
468 self
.undo
.set_saved(flag
)
470 def reset_undo(self
):
471 self
.undo
.reset_undo()
473 def short_title(self
):
474 filename
= self
.io
.filename
476 filename
= os
.path
.basename(filename
)
479 def long_title(self
):
480 return self
.io
.filename
or ""
482 def center_insert_event(self
, event
):
485 def center(self
, mark
="insert"):
487 top
, bot
= self
.getwindowlines()
488 lineno
= self
.getlineno(mark
)
490 newtop
= max(1, lineno
- height
//2)
491 text
.yview(float(newtop
))
493 def getwindowlines(self
):
495 top
= self
.getlineno("@0,0")
496 bot
= self
.getlineno("@0,65535")
497 if top
== bot
and text
.winfo_height() == 1:
498 # Geometry manager hasn't run yet
499 height
= int(text
['height'])
500 bot
= top
+ height
- 1
503 def getlineno(self
, mark
="insert"):
505 return int(float(text
.index(mark
)))
507 def close_event(self
, event
):
512 return self
.io
.maybesave()
515 self
.top
.wm_deiconify()
517 reply
= self
.maybesave()
518 if reply
!= "cancel":
523 WindowList
.unregister_callback(self
.postwindowsmenu
)
528 self
.unload_extensions()
529 self
.io
.close(); self
.io
= None
530 self
.undo
= None # XXX
532 colorizing
= self
.color
.colorizing
533 doh
= colorizing
and self
.top
534 self
.color
.close(doh
) # Cancel colorization
537 self
.per
.close(); self
.per
= None
541 def load_extensions(self
):
543 self
.load_standard_extensions()
545 def unload_extensions(self
):
546 for ins
in self
.extensions
.values():
547 if hasattr(ins
, "close"):
551 def load_standard_extensions(self
):
552 for name
in self
.get_standard_extension_names():
554 self
.load_extension(name
)
556 print "Failed to load extension", `name`
558 traceback
.print_exc()
560 def get_standard_extension_names(self
):
561 return idleconf
.getextensions()
563 def load_extension(self
, name
):
564 mod
= __import__(name
, globals(), locals(), [])
565 cls
= getattr(mod
, name
)
567 self
.extensions
[name
] = ins
568 kdnames
= ["keydefs"]
569 if sys
.platform
== 'win32':
570 kdnames
.append("windows_keydefs")
571 elif sys
.platform
== 'mac':
572 kdnames
.append("mac_keydefs")
574 kdnames
.append("unix_keydefs")
576 for kdname
in kdnames
:
577 if hasattr(ins
, kdname
):
578 keydefs
.update(getattr(ins
, kdname
))
580 self
.apply_bindings(keydefs
)
581 for vevent
in keydefs
.keys():
582 methodname
= vevent
.replace("-", "_")
583 while methodname
[:1] == '<':
584 methodname
= methodname
[1:]
585 while methodname
[-1:] == '>':
586 methodname
= methodname
[:-1]
587 methodname
= methodname
+ "_event"
588 if hasattr(ins
, methodname
):
589 self
.text
.bind(vevent
, getattr(ins
, methodname
))
590 if hasattr(ins
, "menudefs"):
591 self
.fill_menus(ins
.menudefs
, keydefs
)
594 def apply_bindings(self
, keydefs
=None):
596 keydefs
= self
.Bindings
.default_keydefs
598 text
.keydefs
= keydefs
599 for event
, keylist
in keydefs
.items():
601 apply(text
.event_add
, (event
,) + tuple(keylist
))
603 def fill_menus(self
, defs
=None, keydefs
=None):
604 # Fill the menus. Menus that are absent or None in
605 # self.menudict are ignored.
607 defs
= self
.Bindings
.menudefs
609 keydefs
= self
.Bindings
.default_keydefs
610 menudict
= self
.menudict
612 for mname
, itemlist
in defs
:
613 menu
= menudict
.get(mname
)
616 for item
in itemlist
:
621 checkbutton
= (label
[:1] == '!')
624 underline
, label
= prepstr(label
)
625 accelerator
= get_accelerator(keydefs
, event
)
626 def command(text
=text
, event
=event
):
627 text
.event_generate(event
)
629 var
= self
.getrawvar(event
, BooleanVar
)
630 menu
.add_checkbutton(label
=label
, underline
=underline
,
631 command
=command
, accelerator
=accelerator
,
634 menu
.add_command(label
=label
, underline
=underline
,
635 command
=command
, accelerator
=accelerator
)
637 def getvar(self
, name
):
638 var
= self
.getrawvar(name
)
642 def setvar(self
, name
, value
, vartype
=None):
643 var
= self
.getrawvar(name
, vartype
)
647 def getrawvar(self
, name
, vartype
=None):
648 var
= self
.vars.get(name
)
649 if not var
and vartype
:
650 self
.vars[name
] = var
= vartype(self
.text
)
653 # Tk implementations of "virtual text methods" -- each platform
654 # reusing IDLE's support code needs to define these for its GUI's
657 # Is character at text_index in a Python string? Return 0 for
658 # "guaranteed no", true for anything else. This info is expensive
659 # to compute ab initio, but is probably already known by the
660 # platform's colorizer.
662 def is_char_in_string(self
, text_index
):
664 # Return true iff colorizer hasn't (re)gotten this far
665 # yet, or the character is tagged as being in a string
666 return self
.text
.tag_prevrange("TODO", text_index
) or \
667 "STRING" in self
.text
.tag_names(text_index
)
669 # The colorizer is missing: assume the worst
672 # If a selection is defined in the text widget, return (start,
673 # end) as Tkinter text indices, otherwise return (None, None)
674 def get_selection_indices(self
):
676 first
= self
.text
.index("sel.first")
677 last
= self
.text
.index("sel.last")
682 # Return the text widget's current view of what a tab stop means
683 # (equivalent width in spaces).
685 def get_tabwidth(self
):
686 current
= self
.text
['tabs'] or TK_TABWIDTH_DEFAULT
689 # Set the text widget's current view of what a tab stop means.
691 def set_tabwidth(self
, newtabwidth
):
693 if self
.get_tabwidth() != newtabwidth
:
694 pixels
= text
.tk
.call("font", "measure", text
["font"],
695 "-displayof", text
.master
,
697 text
.configure(tabs
=pixels
)
700 # Helper to extract the underscore from a string, e.g.
701 # prepstr("Co_py") returns (2, "Copy").
714 def get_accelerator(keydefs
, event
):
715 keylist
= keydefs
.get(event
)
719 s
= re
.sub(r
"-[a-z]\b", lambda m
: m
.group().upper(), s
)
720 s
= re
.sub(r
"\b\w+\b", lambda m
: keynames
.get(m
.group(), m
.group()), s
)
721 s
= re
.sub("Key-", "", s
)
722 s
= re
.sub("Control-", "Ctrl-", s
)
723 s
= re
.sub("-", "+", s
)
724 s
= re
.sub("><", " ", s
)
725 s
= re
.sub("<", "", s
)
726 s
= re
.sub(">", "", s
)
730 def fixwordbreaks(root
):
731 # Make sure that Tk's double-click and next/previous word
732 # operations use our definition of a word (i.e. an identifier)
734 tk
.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
735 tk
.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
736 tk
.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
744 filename
= sys
.argv
[1]
747 edit
= EditorWindow(root
=root
, filename
=filename
)
748 edit
.set_close_hook(root
.quit
)
752 if __name__
== '__main__':